Introduction

En el cuaderno anterior aprendiste a instalar R, RStudio y las librerías que utilizaremos en durante este taller. También te explique que siempre que veas una caja gris con código dentro de ella (como la que hay a continuación), debes seleccionar el contenido, cortarlo y pegarlo en el editor de RStudio y que debía ejecutarlo bien con la combinación CTRL/CMD+⏎ (CTRL en Windows; CMD en Mac) o haciendo clic en el icono Run que hay en la parte superior derecha de la ventana del editor.

Vamosa comenzar estableciendo los parámetros básicos de esta sesión

# Establecer las opciones
options(stringsAsFactors = F)                           
options(scipen = 999) 
options(max.print=100) 

y cargando las librería que usaremos.

library(tidyverse)
library(tidytext)
library(quanteda)
library(stopwords)

Trabajar con textos

Aunque R se creó para el análisis estadístico y la visualización de los resultados de esos análisis, también es útil para manejar datos textuales que podemos convertir en datos estadísticos y visualizarlos.

Cargar datos textuales

Un corpus clásico para enseñar los principios de la manipulación de textos con R es el State of the Union Address (SOTU), un informe anual que, desde 1790, presenta el Presidente de los Estados Unidos en un sesión conjunta del Congreso. Lo vamos a utilizar, aunque también podríamos hacer uso de los mensajes de Navidad que pronuncia el Jefe del Estado en de Nochebuena, e incluso los debates del Estado de la Nación o los Debates de Investidura, o cualquier otro conjunto (corpus) de datos que se quiera explorar.

All todos los SOTU se pueden conseguir en la red, hay varias fuentes que los han publicado. Sin embargo, los he cosechado y he hecho una pequeña labor de limpieza y están disponibles en un repositorio de GitHub. Para cargarlos se puede utilizar la función read_Lines cuyo primer argumento es el nombre del fichero que contiene el texto (en este caso es una dirección —URL— de internet). Minemos duranto un ratito el discurso de 2022.

Lo primero de todo es cargarlo en un objeto que llamaremos last_sotu.

last_sotu <- read_lines(url("https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/242.txt"))

# Inspeccionemos el texto (solo verás la primera línea.)

str(last_sotu)
 chr [1:101] "Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. "| __truncated__ ...

Una vez que lo hemos guardado en un objeto, es muy sencillo extraer información acerca de la frecuencia de las palabras que contiene y crear listas de palabras. Para lograrlo lo primero que hay que hacer es utilizar la función unnest_tokens, que dividirá el texto en palabras (tokens) y después usaremos la funcición count para conocer las frecuencias absolutas de cada palabra tipo.

Lo primero que hay que hacer es transformar ese extenso texto en una especie de tabla con la función tibble. Esto hará que podamos manejarlo con mayor facilidad.

tidy_sotu <- tibble(text = last_sotu)

# Inspeccionamos los datos (solo las 10 primeras entradas)

tidy_sotu

ATENCIÓN
Los nombres de los objetos los voy a utilizar en inglés, pero nada impide que uses palabras de la lengua que mejor que convenga. Solo hay unas pequeñas limitaciones: 1) no pueden empezar por un número; 2) distingue entre mayúsculas y minúsculas —no es lo mismo Ciudad que ciudad que CIUDAD son tres nombres nombre diferentes— y 3) hay una serie de caracteres que no se pueden utilizar (los símbolos matemáticos, las barras, y algunos otros).
Y una recomendación: no uses letras con tildes, pueden ser fuente de tremendos quebraderos de cabeza.


Cada línea de esta tabla corresponde a un párrafo del discurso, es decir, incluye todo el texto que hay antes de cada golpe de la tecla ⏎. Ahora lo podemos dividir en tokens.

Siempre que veas una tabla con la de antes, puedes recorrerla haciendo clic en el Next que hay en la parte inferior de la misma.

tidy_words <- tidy_sotu %>%
  unnest_tokens(word, text) %>%
  count(word, sort=T)

# Inspeccionamos los datos (solo las 10 primeras entradas)

tidy_words

Lo que le hemos dicho a R es que divida el texto que hay en el objeto tidy_sotu en palabras (tokens), que las cuente, las ordene de mayor a menor frecuencia (n) y las guarde en otro object llamado tidy_words.

Fíjate que en el margen inferior, en el lado izquierdo dice 1-10 of 1.735 rows. Esto quiere decir que el texto del discurso de 2022 tiene 1735 palabras diferentes (palabras tipo), mientras que la columna n indica cuentas veces aparece cada una de las palabras tipo.

Tan fácil como dividir el texto en palabras individuales, es el extraer los n-grams, es decir, las secuencias de n palabras consecutivas. Es sencillo porque la función unnest_tokens tiene un argumento llamado token por medio del cual le puedes indicar a R que quieres extraer ngrams. Si es lo que te interesa, entonces debes especificar el número de palabras que que ha de agrupar; para ello se utiliza el argumento n. Con el código que hay a continuación le pides a R que quieres extraer todos los grupos de cuatro palabras (4-grams) y que informe de la frecuencia absoluta y que las presente en orden decreciente.

tidy_sotu %>%
  unnest_tokens(word,
                text,
                token = "ngrams",
                n = 4) %>%
  count(word, sort=T)

También es posible dividir el texto en oraciones (no enredemos con los coneptos de oración, aquí funcionan de otra manera) en vez de en palabras. De nuevo es fácil de hacer porque el argumento tokens de la función unnest_tokens puede tener el valor sentences.

tidy_sotu %>%
  unnest_tokens(sentences,
                text,
                token = "sentences")

ATENCIÓN
Cuando se le pide que divida cualquier texto en palabras, n-grams u oraciones, una de las cosas que hace es convertir las mayúsculas en minúsculas de manera que First en First Lady y la de cualquier caso de first que pueda aparecer a lo largo del texto las cuente como la misma palabra tipo. Sé que esto es problemático en algunos casos, pero hay herramientas para solucionarlo.


Ya hemos visto que en el discurso de Biden de 2022 había 1735 palabras diferentes (palabras tipo), y vimos que cada una de ella aparece un número determinados de veces (the aparece 300 veces, and 264 y to 234), pero no sabes cuántas son las palabras que conforman el discurso. Hay 6553 palabras token. La forma de calcularlo es sumando todos los valores de n del objeto tidy_words y se consigue con la función sum.

sum(tidy_words$n)
[1] 6553

Stopwords

Al dividir el texto en tokens, has visto que palabras tan usuales como “the”, “to”, “and”, “of” y “we” están en lo alto de la tabla. Estas palabras poco pueden decir del contenido del discurso (en otros tipos de análisis estas palabras son oro molido). Vas a borrarlas.

data(stop_words)
tidy_words %>%
  anti_join(stop_words)

A pesar de haber elminado todas las palabras de función o palabras vacías hay, sin embargo, algunas palabras muy corrientes y contracciones como “let’s”, “that’s”, “it’s” o “we’re”. Quizá te interese localizar todas aquellas palabras que se usan con muchísima más frecuencia en este texto de lo que se utilizan en un basto corpus del inglés. Para conseguirlo, necesitas un dataset que recopile estas frecuencias. Uno muy bueno para eso es el de Peter Norvig’s que se basa en el Google Web Trillion Word Corpus, que se ha construido con textos extraidos de sitios web en inglés.

word_frequencies <- read_csv("https://raw.githubusercontent.com/programminghistorian/jekyll/gh-pages/assets/basic-text-processing-in-r/word_frequency.csv")
head(word_frequencies)

La primera columna indica la lengua de que se trata (es siempe “en” = inglés), la segunda presenta las palabras y la tercera ofrece el porcentaje de uso de cada palabra en el Trillion Word Corpus.

Para combinar todas estas frecuencias con la tabla en la que tienes con los datos del discurso de Biden de 2022, usarás la función inner_join. esta función toma dos tablas (o dos dataset) y los combina en una sola en virtud de las columnas que tengan el mismo nombre; en este caso la columna común es la llamada word.

tidy_words %>%
  inner_join(word_frequencies) %>%
  filter(frequency < 0.1)

Esta lista ya empieza a tener un aspecto mejor. Términos como “america”, “american(s)”, “year”, “people” y “world” han escalado hasta la parte superior de la tabla y ya podemos especular que aparecen con muchas frecuencia en discursos de políticos, como es el caso del SOTU, pero su ocurrencia es relativamente menor en otros dominios. Si establecieras un umbral mucho más bajo, digamos del 0.02, podrás ver un resumen mucho más interesante del discurso.

tidy_words %>%
  inner_join(word_frequencies) %>%
  filter(frequency < 0.02)

Si haces clic en el Next de la parte baja de la tabla anterior, verás que aparecen algunos términos interesantes como “putin”, “ukrainian”, “allies”, “pandemic”, “deficit” e “inflation”. Estos parecen ser algunos de los temas del discurso.

Antes de meterte en agua más profundas, vas a ver un breve resumen basado en las cinco palabras más frecuentes de este discurso. para conseguirlo, necesitas otra tabla con una serie de metadatos que te permitan identificar totalmente cada uno de los discursos. Esta tabla contiene el nombre del Presidente, el año del discurso, años en los que estuvo en la Casa Blanca (algo realmente irrelevante), partido al que pertenece y tipo de discurso, pues pueden escritos o hablados. Vas a cargarlo en tu ordenador.

sotu_meta <- read_tsv("https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu_meta.txt")
# Muestra el contenido (solo el principio)
sotu_meta

Ya es hora de resumirlo en una sola línea. Al final de la instrucción hay un 5, lo cual implica que seleccionará las cinco palabras más utilizadas, se puede incrementar a la cantidad que quieras, pero un sumario de más de 10 palabras quizá sea muy poco informativo).

address_summary <- tidy_words %>%
  inner_join(word_frequencies) %>%
  filter(frequency < 0.02)
result <- c(sotu_meta$president[242],
            sotu_meta$year[242],
            address_summary$word[1:5])
paste(result, collapse = "; ")
[1] "Joe Biden; 2022; americans; tonight; families; pass; costs"

Creo que se podía haber eliminado la palabra “tonight”, no dice nada del tema, tan solo que lo leyó una tarde noche.

Concordancias KWIC

Crear unas concordancias o tablas de palabras clave en contexto key-word-in-context (KWIC) es uno de los procedimientos más usuales cuando se analizan datos textuales. No tienes que esforzarte mucho para conseguirlo, hay varias funciones que lo harán con sencillez. Utilizará la función kwic de la librería quanteda para crear una concordancia KWIC, pero exige dividir el texto en tokens con la función tokens, pero de la propia librería quanteda.

kwic_multiple <- last_sotu %>%
  tokens() %>%
  kwic(pattern = "Ukraine",
       window = 3) %>%
  as.data.frame()
# Inspecciona el comienzo de la tabla
head(kwic_multiple)

Analizar todos y cada uno de los Discursos del State of the Union desde 1790 hasta 2024

El primer paso para analizar todos el corpus de los discursos del State of the Union es hacer que R los lea todos a la vez. Esto implica utilizar, como has hecho antes, la función readLines, pero unirás todos los párrafos de cada uno de los discursos con la función paste, añadirás un sistema de identificación para saber qué discurso es cada uno de ellos. Todo esto se tiene que hacer por medio de un bucle, porque quieres meter nada más y nada menos que 244 discursos en una única tabla (u objeto) llamada all_sotu.

Puesto que los ficheros se encuentran en la web, el primer paso es declarar la url completa de cada discurso y crear una tabla vacía (all_sotu) para guardar todos los teextos.

La instrucción para crear los nombrs de los ficheros es un proceso que requiere dos pasos. En primer lugar, se ha de guardar (almacenar) la parte fija de la URL en el objeto base_url. Y, en segundo lugar, hay que crear los nombres de los ficheros que están construidos por un número entre 1 y 244. Está es, aparentemente, la parte más complicadilla, pues la secuencia de 1 a 244 debe estar precedido por uno (para los números 10 a 99) o dos (para los números entres el 1 y el 9) ceros. La función ideal para esto es sprintf, que permite una fórmula para añadir los números con los ceros a la izquierda, en nuestro caso entre 001099244, que incorpore la extensión .txt y que la añada al final de la parte fija de la url.

base_url <- "https://raw.githubusercontent.com/7PartidasDigital/MLex/master"
files <- sprintf("%s/sotu/%03d.txt", base_url, 1:244)
all_sotu <- NULL

Quizá te estés preguntado qué aspecto tienen las URL que acabas de crear. Vas a echarle una ojeada a las seis primeras con la función head.

head(files)
[1] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/001.txt"
[2] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/002.txt"
[3] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/003.txt"
[4] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/004.txt"
[5] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/005.txt"
[6] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/006.txt"

y las seis últimas con la función tail.

tail(files)
[1] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/239.txt"
[2] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/240.txt"
[3] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/241.txt"
[4] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/242.txt"
[5] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/243.txt"
[6] "https://raw.githubusercontent.com/7PartidasDigital/MLex/master/sotu/244.txt"

Ahora ya puedes leer todos los discursos e incorporarlos a la tabla (le llevará unos dos minutos o un poquito más.)

for (i in 1:length(files)) {
  sotu <- readLines(files[i])
  sotu <- paste(sotu, collapse = "\n")
  temporary <- tibble(speech = i,
                      text = sotu)
  all_sotu <- bind_rows(all_sotu, temporary)
}

Además, le vaas a añadir los metadatos que tienes guardados en sotu_meta:

all_sotu <- full_join(all_sotu, sotu_meta, by="speech")

Análisis exploratorio

ya tienes todos los elementos que necesitas para analizar todos los discursos SOTU de los últimos 244 años. Lo primero es dividirlos en tokens. Ya has visto que ña función unnest_tokens los puede hacer en poquísimo tiempo.

all_words <- all_sotu %>%
  unnest_tokens(word, text)
all_words

Tienes al alcance de los dedos un corpus de poco más de 2.000.000 de palabras (exactamente 2.017.504 tokens). Ahora puedes plantearte muchas preguntas como ¿Hay un patrón temporal que pueda explicar la diferente extensión de cada discurso? ¿Cómo se compara la extensión de los discursos de las últimas administraciones con los de Franklin D. Roosevelt, Abraham Lincoln y George Washington?

La mejor manera para observar esto es con un gráfico de dispersión. Se puede dibujar con la función ggplot, para ello pondrás el año en el eje X (el horizontal) y la longitud, entendida como el número de palabras o tokens n en el eje vertical (o y).

all_words %>%
  group_by(year) %>%
  count() %>%
  ggplot() +
  geom_point(aes(year,
                 n))

Parece ser que entre 1790 y 1850 la longitud de los discursos aumentó progresivamente la longitud de los discursos, durante unos pocos anos decreción, y volvieron a crecar hasta finales del siglo XIX. La extensión disminuyó drásticamente en la época de la I Gerra Mundial, aunque a lo largo del siglo XX hay una puñado de discursos muy extensos. Hacia 2001 disminuyeron y comenzaron a alargarse de nuevo. ¿Hay alguna razón para este vaivén en las longitud de los discursos. Has visto que algunos presidentes predentaron sus discursos por escrito mientras que otros los leyeron. ¿Es esta la razón? Puedes averiguarlo en segundos. Para ello vas a emplear el argumento color para que los coloree de acuerdo con el valor de la variable sotu_type.

all_words %>%
  group_by(year) %>%
  count() %>%
  left_join(sotu_meta) %>%
  ggplot() +
  geom_point(aes(year,
                 n,
                 color = sotu_type))

Pues eso parece. El incremento que se da a lo largo del siglo XIX se debe a que se pasó de un discurso oral a otro escrito. El cambio radical se produjo cuando cuando Woodrow Wilson (en 1913) volvió al origen, y lo hizo oralmente en el Congreso de Estados Unidos. Como puedes ver, los outliers (los más largos) se presentaron todos por escrito tras la Segunda Guerra Mundial.

Otra pregunta que te puedes hacer es si el pertenecer a uno u otro partido tiene alguna influencia en la extensión de los discursos. Lo puedes averiguar en segundos con tan solo cambiar con cambiar que el argumento color use party en vez de sotu_type.

all_words %>%
  group_by(year) %>%
  count() %>%
  left_join(sotu_meta) %>%
  ggplot() +
  geom_point(aes(year,
                 n,
                 color = party))

No parece que el ser de de uno u otro partidos tenga nada que ver con la extensión. Lo más curioso es que desde finales de la década de 1990 los discursos más extensos fueron de presisdentes Demócratas.

Como veís, hasta ahora solo nos hemos planteado preguntas que se podrían hacer un estudiante de políticas o de historia, pero podemos hacernos otras que interesen a otros especialistas.

Has visto que puedes dividir el texto en oraciones. la longitud de estas puede ser un posible rasgo estilo, e incluso puede hablar de la complejidad del texto. Así que vamos a dividirlos en oraciones y contar cuántas palabras tiene cada uno de ellos.

all_sentences <- all_sotu %>%
    unnest_tokens(sentence,
                  text,
                  token = "sentences") %>%
  mutate(NumberWords = str_count(sentence,
                            pattern = "\\w+"))

Calculemos la mediana y vamos a verla un una gráfica, es más claro que una tabla con 244 líneas de números.

all_sentences %>%
  group_by(year) %>%
  mutate(median = median(NumberWords)) %>%
  left_join(sotu_meta) %>%
  ggplot() +
  geom_point(aes(year,
                 median))

La gráfica muestra que, a lo largo del tiempo, hay una tendencia general a usar oraciones cada vez más cortas. Recuerda que algunos de los discursos tras la Segunda Guerra Mundial fueron escritos, como la gran mayoría de los del siglo XIX. Está claro que a mayor extensión del discurso, a pesar de estar escrito, nada tiene que ver con la longitud de las oraciones. Es evidente que los discursos de los presidentes nortamericanos han tendido a usar oraciones cada vez más cortas.

Para verlo de una manera más evidente, es posible incorporar una línea que marque la tendencia con la función geom_smooth. Este tipo de línea son de gran ayuda en muchas gráficas. Tienen un doble proósito: marcan la tendencia general a lo largo de una serie temporal y muestran con mayor claridad los datos atípicos.

all_sentences %>%
    group_by(year) %>%
    mutate(median = median(NumberWords)) %>%
    left_join(sotu_meta) %>%
    ggplot(aes(x = year,
               y = median)) +
    geom_point() +
    geom_smooth()

¡AVISO! Es cierto que hay una tendencia general a acortar las oraciones, pero hay un pequeño problema que no he considerado y que podría tener cierta influencia en los resultados finales, por lo que debéis tenerlo en cuenta. El toquenizador, es decir, las instrucciones que dividen el texto en oraciones considera que el límite de las oraciones son uno de estos tres casos: puntos, signo de admiración y signos de interrogación. A lo largo de estos textos hay unas cuantas abreviaturas, como Mr., como se puede ver en esta tabla.

all_words %>%
  filter(str_detect(word, regex("\\bmr\\b", ignore_case = TRUE))) %>%
  select(word)

Las palabras más características de cada presidente (Palabras clave)

Has visto los bigramas más frecuentes de cada uno de los presidentes, pero no son nada ilustrativos, pues no ofrecen las palabras más interesantes de cada uno de ellos.

Vas a analizar los discursos de cada presidente, pero no desde el punto de vista de los discrusos individuales, sino del conjunto de cada uno de ellos.La talba que hay a continuación te mostrará que la palabra más frecuente de Roosevelt es el artículo “the” y la seguna más utilizada es la preposición “of”. Son tan corrientes que no tienen ningún interés.

all_words_by_president <- all_sotu %>%
  unnest_tokens(word, text) %>%
  count(president, word, sort = T)

# inspect data (top 10 sorted)

all_words_by_president

Sin embargo, hay una manera de localizar las palabras más importantes de cada subcorpus (serie de discursos pronunciados por cada presidente) y se consigue comparándolos entre sí. Las mátemáticas que hay tras esto son de infarto, pero una de las cosas admirables de R y de todas las librería que existen es que ha habido quienes se han ocupado de ofrecernos funciones sencillas de usar. Uno de los métodos para extraer las palabras mñas importantes o característica de cada presidente (o de cualquier otro texto) es el llamado Term Frequency–Inverse Document Frequency, o tf-idf para abreviar. Es una análisis estadístico que calcula cuáles son las palabras clave de un texto y, por lotanto, reflejan cuán características son dentro de un texto dado. El Term Frequency–Inverse Document Frequency calcula la frecuencia de cada palabra de un texto y la compara con la frecuencia que ofrece en los documentos en los que aparece. la explicación no es muy clara, pero cómo hallar los valores, y por tanto las palabras, es algo sencillo gracias a la función bind_tf_idf.

all_words_by_president %>%
  bind_tf_idf(word, president, n) %>%
  mutate(president = factor(president,
                            levels = c("George Washington", "John Adams", "Thomas Jefferson",
                                       "James Madison", "James Monroe", "John Quincy Adams",
                                       "Andrew Jackson", "Martin Van Buren", "John Tyler",
                                       "James K. Polk", "Zachary Taylor", "Millard Fillmore",
                                       "Franklin Pierce", "James Buchanan", "Abraham Lincoln",
                                       "Andrew Johnson", "Ulysses S. Grant", "Rutherford B. Hayes",
                                       "Chester A. Arthur", "Grover Cleveland", "Benjamin Harrison",
                                       "William McKinley", "Theodore Roosevelt", "William Howard Taft",
                                       "Woodrow Wilson", "Warren G. Harding", "Calvin Coolidge",
                                       "Herbert Hoover", "Franklin D. Roosevelt", "Harry S Truman",
                                       "Dwight D. Eisenhower", "John F. Kennedy", "Lyndon B. Johnson",
                                       "Richard M. Nixon", "Gerald R. Ford", "Jimmy Carter",
                                       "Ronald Reagan", "George Bush", "William J. Clinton",
                                       "George W. Bush", "Barack Obama", "Donald Trump", "Joe Biden"))) %>%
  group_by(president) %>% 
  slice_max(tf_idf, n = 10) %>% 
  ungroup() %>%
  mutate(word = reorder(word, tf_idf)) %>%
  ggplot(aes(tf_idf, word, fill = president)) +
  geom_col(show.legend = FALSE) +
  labs(x = "tf-idf", y = NULL) +
  facet_wrap(~president, ncol = 4, scales = "free")

Hay demasiados cifras, pueden que tengan algún significado, pero son un sinsentido para nosotros. Así que borrémoslos. Para hacerlo hay que rehacer parte del trabajo. Hay que dividir en palabras todos los dicursos, pero lo primero que hay que hacer es borrar todos los números con la función mutate.

all_words_by_president <- all_sotu %>%
  mutate(text = str_remove_all(text, "[:digit:]")) %>%
  unnest_tokens(word, text) %>%
  count(president, word, sort = T)

Una vez hecho, hay que redibujar el gráfico. No hay que hacer nada nuevo.

all_words_by_president %>%
  bind_tf_idf(word, president, n) %>%
  mutate(president = factor(president,
                            levels = c("George Washington", "John Adams", "Thomas Jefferson",
                                       "James Madison", "James Monroe", "John Quincy Adams",
                                       "Andrew Jackson", "Martin Van Buren", "John Tyler",
                                       "James K. Polk", "Zachary Taylor", "Millard Fillmore",
                                       "Franklin Pierce", "James Buchanan", "Abraham Lincoln",
                                       "Andrew Johnson", "Ulysses S. Grant", "Rutherford B. Hayes",
                                       "Chester A. Arthur", "Grover Cleveland", "Benjamin Harrison",
                                       "William McKinley", "Theodore Roosevelt", "William Howard Taft",
                                       "Woodrow Wilson", "Warren G. Harding", "Calvin Coolidge",
                                       "Herbert Hoover", "Franklin D. Roosevelt", "Harry S Truman",
                                       "Dwight D. Eisenhower", "John F. Kennedy", "Lyndon B. Johnson",
                                       "Richard M. Nixon", "Gerald R. Ford", "Jimmy Carter",
                                       "Ronald Reagan", "George Bush", "William J. Clinton",
                                       "George W. Bush", "Barack Obama", "Donald Trump", "Joe Biden"))) %>%
  group_by(president) %>% 
  slice_max(tf_idf, n = 10) %>% 
  ungroup() %>%
  mutate(word = reorder(word, tf_idf)) %>%
  ggplot(aes(tf_idf, word, fill = president)) +
  geom_col(show.legend = FALSE) +
  labs(x = "tf-idf", y = NULL) +
  facet_wrap(~president, ncol = 4, scales = "free")

Ahora los gráficos tienen algo más de sentido y depende de ti la interpretación. Hay una serie de problemas que debes tener en cuenta . Si te fijas en el gráfico de Kennedy verás que aparecen “viet” y “nam”, pero son una sola palabra “viet-nam”. Lo mismo sucede en el de Bush Jr. “al-Qaida” se ha dividido en los segmentos “al” y “qaida”. Por lo tanto, cuando uses estas técnicas para minar textos, tienes que tener en cuenta los problemas que puede haber escondidos en los textos.

Colocaciones

Según Altenberg (1991: 128), “Aproximadamente el 70% de las palabras que constituyen un corpus forman parte de combinaciones de palabras usuales”. La investigación de tales combinaciones de palabras en corpus se remonta a los primeros estudios de Firth (1957) sobre colocaciones, quien resumió este principio con una sucinta frase: “reconocerás una palabra por la compañía que tiene”.

En esta sección vas a ver todas las colocaciones que ofrecen los discursos presidenciales. Ya has visto que se puede dividir un textos en tokens, pero también en n-grams, es decir, secuencias de n-palabras o n-tokens). Dividamos todos los discursos en bigramas, en secuencias de dos palabras / tokens.

all_bigrams <- all_sotu %>%
  unnest_tokens(bigram,
                text,
                token = "ngrams",
                n = 2)

# inspect data (top 10 sorted)

all_bigrams %>%
  count(bigram, sort = T)

Como ya has visto, el resultado no es muy ilustrativo. De nuevo son las palabras gramaticales —preposiciones, artículos, conjunciones…— las que aparecen en primer lugar. La única excepción, esperable, es United States. Podríamos estar tentados de usar la función anti_join() para borrarlas. Sin embargo, no lo puedes hacer directamente puesto que la lista de palabras vacías, o stopwords que has cargado solo contienen unigramas, no hay ni un solo bigramas, por lo que no funcionaría.

La solución se, una vez aue tenemos los bigramas, separar los dos constituyentes y borrar las palabraas vacías. Aunque se puede hacer todo con un solo bloque de código, lo voy a presentar en varios pasos, para que puedas ver la lógica del procedimiento.

En primer lugar vasa separar los dos elementos con la función separate() y los vas a conservar en dos caloumnas que llamaremos word1 y word2.

separated_bigrams <- all_bigrams %>%
  separate(bigram,
            c("word1", "word2"),
            sep = " ")

# inspect data (top 10 sorted)

separated_bigrams %>%
  count(word1, word2, sort = T)

Tiene casi el mismo aspecto que la tabla que se imprimió cuando dividimos los texto en bimagras, pero cada una de las partes está en una columna (variable) diferente.

El siguiente paso es borrar todas las palabras vacías, pero no puedes usar la función anti_join. Vas a extraer todas las palabras vacías con %in%, tanto de la columna word1 como de word2, y que no se encuentren la columna –!– en la columna word dle objeto stop_words. es un galimatías, lo sé.

filtered_bigrams <- separated_bigrams %>%
  filter(!word1 %in% stop_words$word,
         !word2 %in% stop_words$word)

# inspect data (top 10 sorted)

filtered_bigrams %>%
  count(word1, word2, sort = T)

Aunque te lo pueda parecer, no estás viendo bigramas, tan solo estás viendo dos palabras contiguas en dos columanas diferentes. Hay que reconstruir los bimagras y se consigue con la función unite(), que reune en una sola columna los valores que haya en dos o más columnas.

united_bigrams <- filtered_bigrams %>%
  unite(bigram, word1, word2, sep = " ")

# inspect data (top 10 sorted)

united_bigrams %>%
  count(bigram, sort = T)

Ahora podemos trazar un gráfico que nos permita ver qué bigramas utiliza con mayor frecuencia cada presidente. Le llevará un poco de tiempo dibujarlo ().

united_bigrams %>%
  mutate(president = factor(president,
                            levels = c("George Washington", "John Adams", "Thomas Jefferson",
                                       "James Madison", "James Monroe", "John Quincy Adams",
                                       "Andrew Jackson", "Martin Van Buren", "John Tyler",
                                       "James K. Polk", "Zachary Taylor", "Millard Fillmore",
                                       "Franklin Pierce", "James Buchanan", "Abraham Lincoln",
                                       "Andrew Johnson", "Ulysses S. Grant", "Rutherford B. Hayes",
                                       "Chester A. Arthur", "Grover Cleveland", "Benjamin Harrison",
                                       "William McKinley", "Theodore Roosevelt", "William Howard Taft",
                                       "Woodrow Wilson", "Warren G. Harding", "Calvin Coolidge",
                                       "Herbert Hoover", "Franklin D. Roosevelt", "Harry S Truman",
                                       "Dwight D. Eisenhower", "John F. Kennedy", "Lyndon B. Johnson",
                                       "Richard M. Nixon", "Gerald R. Ford", "Jimmy Carter",
                                       "Ronald Reagan", "George Bush", "William J. Clinton",
                                       "George W. Bush", "Barack Obama", "Donald Trump", "Joe Biden"))) %>%
  count(president, bigram, sort = T) %>%
  group_by(president) %>%
  top_n(5) %>%
  ggplot() +
  geom_col(aes(y = n , x = reorder(bigram,n)),
           fill = "maroon") +
  coord_flip() +
  facet_wrap(~ president, ncol = 4, scales = "free")

Hay muchas otras posibilidades, como el análisis morfológico (Part-of-Speech (PoS) tagging) que permite identificar a qué clase de palabras pertenece cada una de ellas (p. e., sustantivo, adjectivo, verbo, etc.). Lo que se consigue es añadir a cada palabra del texto la etiqueta gramatical. Pero no tenemos tiempo. Lo único que me importa es que que este taller os haya despertado el interés por hacer minería de textos con el uso de R. Las posibilidades son enormes. Sé que lo he limitado a textos en inglés, pero puedes usar la lengua que te interese.

¡RECUERDA!

sessionInfo()

NOTE
Este cuaderno se basa en algunos tutoriales de Ladal, Programming Historian y CuentaPalabras.
Para quienes tengan intereses lingüísticos, recomiendo los tutoriales Ladal.


PID2020-112621GB-I00/AEI/10.13039/501100011033
LS0tCnRpdGxlOiAiUiBwYXJhIGxvcyBkZSBMZXRyYXM6IFRyYWJhamFuZG8gY29uIHRleHRvcyIKYXV0aG9yOiAiSi4gTS4gRnJhZGVqYXMiCmRhdGU6ICIyMDI0LjEyLjE5IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCjxkaXY+CjxjZW50ZXI+CjxpbWcgc3JjPSJodHRwczovL2Z5bC51dmEuZXMvd3AtY29udGVudC91cGxvYWRzLzIwMjAvMDcvZnlsdXZhLWxvZ28ucG5nIiB3aWR0aD0iMzUwIi8+CjwvY2VudGVyPgo8L2Rpdj4KCgojIEludHJvZHVjdGlvbgoKRW4gZWwgY3VhZGVybm8gYW50ZXJpb3IgYXByZW5kaXN0ZSBhIGluc3RhbGFyIFIsIFJTdHVkaW8geSBsYXMgbGlicmVyw61hcyBxdWUgdXRpbGl6YXJlbW9zIGVuIGR1cmFudGUgZXN0ZSB0YWxsZXIuIFRhbWJpw6luIHRlIGV4cGxpcXVlIHF1ZSBzaWVtcHJlIHF1ZSB2ZWFzIHVuYSBjYWphIGdyaXMgY29uIGPDs2RpZ28gZGVudHJvIGRlIGVsbGEgKGNvbW8gbGEgcXVlIGhheSBhIGNvbnRpbnVhY2nDs24pLCBkZWJlcyBzZWxlY2Npb25hciBlbCBjb250ZW5pZG8sIGNvcnRhcmxvIHkgcGVnYXJsbyBlbiBlbCBlZGl0b3IgZGUgUlN0dWRpbyB5IHF1ZSBkZWLDrWEgZWplY3V0YXJsbyBiaWVuIGNvbiBsYSBjb21iaW5hY2nDs24gYENUUkwvQ01EK2Dij44gKGBDVFJMYCBlbiBXaW5kb3dzOyBgQ01EYCBlbiBNYWMpIG8gaGFjaWVuZG8gY2xpYyBlbiBlbCBpY29ubyBSdW4gcXVlIGhheSBlbiBsYSBwYXJ0ZSBzdXBlcmlvciBkZXJlY2hhIGRlIGxhIHZlbnRhbmEgZGVsIGVkaXRvci4KClZhbW9zYSBjb21lbnphciBlc3RhYmxlY2llbmRvIGxvcyBwYXLDoW1ldHJvcyBiw6FzaWNvcyBkZSBlc3RhIHNlc2nDs24KCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KIyBFc3RhYmxlY2VyIGxhcyBvcGNpb25lcwpvcHRpb25zKHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSAgICAgICAgICAgICAgICAgICAgICAgICAgIApvcHRpb25zKHNjaXBlbiA9IDk5OSkgCm9wdGlvbnMobWF4LnByaW50PTEwMCkgCmBgYAoKeSBjYXJnYW5kbyBsYXMgbGlicmVyw61hIHF1ZSB1c2FyZW1vcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHN0b3B3b3JkcykKYGBgCgoKIyBUcmFiYWphciBjb24gdGV4dG9zey19CgpBdW5xdWUgUiBzZSBjcmXDsyBwYXJhIGVsIGFuw6FsaXNpcyBlc3RhZMOtc3RpY28geSBsYSB2aXN1YWxpemFjacOzbiBkZSBsb3MgcmVzdWx0YWRvcyBkZSBlc29zIGFuw6FsaXNpcywgdGFtYmnDqW4gZXMgw7p0aWwgcGFyYSBtYW5lamFyIGRhdG9zIHRleHR1YWxlcyBxdWUgcG9kZW1vcyBjb252ZXJ0aXIgZW4gZGF0b3MgZXN0YWTDrXN0aWNvcyB5IHZpc3VhbGl6YXJsb3MuCgoKIyMgQ2FyZ2FyIGRhdG9zIHRleHR1YWxlc3stfQoKVW4gY29ycHVzIGNsw6FzaWNvIHBhcmEgZW5zZcOxYXIgbG9zIHByaW5jaXBpb3MgZGUgbGEgbWFuaXB1bGFjacOzbiBkZSB0ZXh0b3MgY29uIFIgZXMgZWwgX1N0YXRlIG9mIHRoZSBVbmlvbiBBZGRyZXNzXyAoU09UVSksIHVuIGluZm9ybWUgYW51YWwgcXVlLCBkZXNkZSAxNzkwLCBwcmVzZW50YSBlbCBQcmVzaWRlbnRlIGRlIGxvcyBFc3RhZG9zIFVuaWRvcyBlbiB1biBzZXNpw7NuIGNvbmp1bnRhIGRlbCBDb25ncmVzby4gTG8gdmFtb3MgYSB1dGlsaXphciwgYXVucXVlIHRhbWJpw6luIHBvZHLDrWFtb3MgaGFjZXIgdXNvIGRlIGxvcyBtZW5zYWplcyBkZSBOYXZpZGFkIHF1ZSBwcm9udW5jaWEgZWwgSmVmZSBkZWwgRXN0YWRvIGVuIGRlIE5vY2hlYnVlbmEsIGUgaW5jbHVzbyBsb3MgZGViYXRlcyBkZWwgRXN0YWRvIGRlIGxhIE5hY2nDs24gbyBsb3MgRGViYXRlcyBkZSBJbnZlc3RpZHVyYSwgbyBjdWFscXVpZXIgb3RybyBjb25qdW50byAoY29ycHVzKSBkZSBkYXRvcyBxdWUgc2UgcXVpZXJhIGV4cGxvcmFyLgoKQWxsIHRvZG9zIGxvcyBTT1RVIHNlIHB1ZWRlbiBjb25zZWd1aXIgZW4gbGEgcmVkLCBoYXkgdmFyaWFzIGZ1ZW50ZXMgcXVlIGxvcyBoYW4gcHVibGljYWRvLiBTaW4gZW1iYXJnbywgbG9zIGhlIGNvc2VjaGFkbyB5IGhlIGhlY2hvIHVuYSBwZXF1ZcOxYSBsYWJvciBkZSBsaW1waWV6YSB5IGVzdMOhbiBkaXNwb25pYmxlcyBlbiB1biByZXBvc2l0b3JpbyBkZSBHaXRIdWIuIFBhcmEgY2FyZ2FybG9zIHNlIHB1ZWRlIHV0aWxpemFyIGxhIGZ1bmNpw7NuIGByZWFkX0xpbmVzYCBjdXlvIHByaW1lciBhcmd1bWVudG8gZXMgZWwgbm9tYnJlIGRlbCBmaWNoZXJvIHF1ZSBjb250aWVuZSBlbCB0ZXh0byAoZW4gZXN0ZSBjYXNvIGVzIHVuYSBkaXJlY2Npw7NuIOKAlFVSTOKAlCBkZSBpbnRlcm5ldCkuIE1pbmVtb3MgZHVyYW50byB1biByYXRpdG8gZWwgZGlzY3Vyc28gZGUgMjAyMi4KCkxvIHByaW1lcm8gZGUgdG9kbyBlcyBjYXJnYXJsbyBlbiB1biAqKm9iamV0byoqIHF1ZSBsbGFtYXJlbW9zIGBsYXN0X3NvdHVgLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmxhc3Rfc290dSA8LSByZWFkX2xpbmVzKHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzdQYXJ0aWRhc0RpZ2l0YWwvTUxleC9tYXN0ZXIvc290dS8yNDIudHh0IikpCgojIEluc3BlY2Npb25lbW9zIGVsIHRleHRvIChzb2xvIHZlcsOhcyBsYSBwcmltZXJhIGzDrW5lYS4pCgpzdHIobGFzdF9zb3R1KQpgYGAKClVuYSB2ZXogcXVlIGxvIGhlbW9zIGd1YXJkYWRvIGVuIHVuICoqb2JqZXRvKiosIGVzIG11eSBzZW5jaWxsbyBleHRyYWVyIGluZm9ybWFjacOzbiBhY2VyY2EgZGUgbGEgZnJlY3VlbmNpYSBkZSBsYXMgcGFsYWJyYXMgcXVlIGNvbnRpZW5lIHkgY3JlYXIgbGlzdGFzIGRlIHBhbGFicmFzLiBQYXJhIGxvZ3JhcmxvIGxvIHByaW1lcm8gcXVlIGhheSBxdWUgaGFjZXIgZXMgdXRpbGl6YXIgbGEgZnVuY2nDs24gYHVubmVzdF90b2tlbnNgLCBxdWUgZGl2aWRpcsOhIGVsIHRleHRvIGVuIHBhbGFicmFzICh0b2tlbnMpIHkgZGVzcHXDqXMgdXNhcmVtb3MgbGEgZnVuY2ljacOzbiBgY291bnRgIHBhcmEgY29ub2NlciBsYXMgZnJlY3VlbmNpYXMgYWJzb2x1dGFzIGRlIGNhZGEgcGFsYWJyYSB0aXBvLgoKTG8gcHJpbWVybyBxdWUgaGF5IHF1ZSBoYWNlciBlcyB0cmFuc2Zvcm1hciBlc2UgZXh0ZW5zbyB0ZXh0byBlbiB1bmEgZXNwZWNpZSBkZSB0YWJsYSBjb24gbGEgZnVuY2nDs24gYHRpYmJsZWAuIEVzdG8gaGFyw6EgcXVlIHBvZGFtb3MgbWFuZWphcmxvIGNvbiBtYXlvciBmYWNpbGlkYWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGlkeV9zb3R1IDwtIHRpYmJsZSh0ZXh0ID0gbGFzdF9zb3R1KQoKIyBJbnNwZWNjaW9uYW1vcyBsb3MgZGF0b3MgKHNvbG8gbGFzIDEwIHByaW1lcmFzIGVudHJhZGFzKQoKdGlkeV9zb3R1CmBgYAoKPGRpdiBjbGFzcz0id2FybmluZyIgc3R5bGU9J3BhZGRpbmc6MC4xZW07IGJhY2tncm91bmQtY29sb3I6IzFlODQ0OTsgY29sb3I6I2YyZjJmMic+CjxzcGFuPgo8cCBzdHlsZT0nbWFyZ2luLXRvcDoxZW07IHRleHQtYWxpZ246Y2VudGVyJz4KPGI+QVRFTkNJw5NOPC9iPjxicj5Mb3Mgbm9tYnJlcyBkZSBsb3MgKipvYmpldG9zKiogbG9zIHZveSBhIHV0aWxpemFyIGVuIGluZ2zDqXMsIHBlcm8gbmFkYSBpbXBpZGUgcXVlIHVzZXMgcGFsYWJyYXMgZGUgbGEgbGVuZ3VhIHF1ZSBtZWpvciBxdWUgY29udmVuZ2EuIFNvbG8gaGF5IHVuYXMgcGVxdWXDsWFzIGxpbWl0YWNpb25lczogMSkgbm8gcHVlZGVuIGVtcGV6YXIgcG9yIHVuIG7Dum1lcm87IDIpIGRpc3Rpbmd1ZSBlbnRyZSBtYXnDunNjdWxhcyB5IG1pbsO6c2N1bGFzIOKAlG5vIGVzIGxvIG1pc21vIGBDaXVkYWRgIHF1ZSBgY2l1ZGFkYCBxdWUgYENJVURBRGAgc29uIHRyZXMgbm9tYnJlcyBub21icmUgZGlmZXJlbnRlc+KAlCB5IDMpIGhheSB1bmEgc2VyaWUgZGUgY2FyYWN0ZXJlcyBxdWUgbm8gc2UgcHVlZGVuIHV0aWxpemFyIChsb3Mgc8OtbWJvbG9zIG1hdGVtw6F0aWNvcywgbGFzIGJhcnJhcywgeSBhbGd1bm9zIG90cm9zKS48YnI+WSB1bmEgcmVjb21lbmRhY2nDs246IG5vIHVzZXMgbGV0cmFzIGNvbiB0aWxkZXMsIHB1ZWRlbiBzZXIgZnVlbnRlIGRlIHRyZW1lbmRvcyBxdWVicmFkZXJvcyBkZSBjYWJlemEuPGJyPjwvcD4KPHAgc3R5bGU9J21hcmdpbi1sZWZ0OjFlbTsnPgo8L3A+PC9zcGFuPgo8L2Rpdj4KPGJyPgoKQ2FkYSBsw61uZWEgZGUgZXN0YSB0YWJsYSBjb3JyZXNwb25kZSBhIHVuIHDDoXJyYWZvIGRlbCBkaXNjdXJzbywgZXMgZGVjaXIsIGluY2x1eWUgdG9kbyBlbCB0ZXh0byBxdWUgaGF5IGFudGVzIGRlIGNhZGEgZ29scGUgZGUgbGEgdGVjbGEg4o+OLiBBaG9yYSBsbyBwb2RlbW9zIGRpdmlkaXIgZW4gdG9rZW5zLgoKU2llbXByZSBxdWUgdmVhcyB1bmEgdGFibGEgY29uIGxhIGRlIGFudGVzLCBwdWVkZXMgcmVjb3JyZXJsYSBoYWNpZW5kbyBjbGljIGVuIGVsIGBOZXh0YCBxdWUgaGF5IGVuIGxhIHBhcnRlIGluZmVyaW9yIGRlIGxhIG1pc21hLiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0aWR5X3dvcmRzIDwtIHRpZHlfc290dSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGNvdW50KHdvcmQsIHNvcnQ9VCkKCiMgSW5zcGVjY2lvbmFtb3MgbG9zIGRhdG9zIChzb2xvIGxhcyAxMCBwcmltZXJhcyBlbnRyYWRhcykKCnRpZHlfd29yZHMKYGBgCgpMbyBxdWUgbGUgaGVtb3MgZGljaG8gYSBSIGVzIHF1ZSBkaXZpZGEgZWwgdGV4dG8gcXVlIGhheSBlbiBlbCAqKm9iamV0byoqIGB0aWR5X3NvdHVgIGVuIHBhbGFicmFzICh0b2tlbnMpLCBxdWUgbGFzIGN1ZW50ZSwgbGFzIG9yZGVuZSBkZSBtYXlvciBhIG1lbm9yIGZyZWN1ZW5jaWEgKGBuYCkgeSBsYXMgZ3VhcmRlIGVuIG90cm8gb2JqZWN0IGxsYW1hZG8gYHRpZHlfd29yZHNgLgoKRsOtamF0ZSBxdWUgZW4gZWwgbWFyZ2VuIGluZmVyaW9yLCBlbiBlbCBsYWRvIGl6cXVpZXJkbyBkaWNlIGAxLTEwIG9mIDEuNzM1IHJvd3NgLiBFc3RvIHF1aWVyZSBkZWNpciBxdWUgZWwgdGV4dG8gZGVsIGRpc2N1cnNvIGRlIDIwMjIgdGllbmUgMTczNSBwYWxhYnJhcyBkaWZlcmVudGVzIChwYWxhYnJhcyB0aXBvKSwgbWllbnRyYXMgcXVlIGxhIGNvbHVtbmEgYG5gIGluZGljYSBjdWVudGFzIHZlY2VzIGFwYXJlY2UgY2FkYSB1bmEgZGUgbGFzIHBhbGFicmFzIHRpcG8uCgpUYW4gZsOhY2lsIGNvbW8gZGl2aWRpciBlbCB0ZXh0byBlbiBwYWxhYnJhcyBpbmRpdmlkdWFsZXMsIGVzIGVsIGV4dHJhZXIgbG9zIF9uLWdyYW1zXywgZXMgZGVjaXIsIGxhcyBzZWN1ZW5jaWFzIGRlIGBuYCBwYWxhYnJhcyBjb25zZWN1dGl2YXMuIEVzIHNlbmNpbGxvIHBvcnF1ZSBsYSBmdW5jacOzbiBgdW5uZXN0X3Rva2Vuc2AgdGllbmUgdW4gYXJndW1lbnRvIGxsYW1hZG8gYHRva2VuYCBwb3IgbWVkaW8gZGVsIGN1YWwgbGUgcHVlZGVzIGluZGljYXIgYSBSIHF1ZSBxdWllcmVzIGV4dHJhZXIgYG5ncmFtc2AuIFNpIGVzIGxvIHF1ZSB0ZSBpbnRlcmVzYSwgZW50b25jZXMgZGViZXMgZXNwZWNpZmljYXIgZWwgbsO6bWVybyBkZSBwYWxhYnJhcyBxdWUgcXVlIGhhIGRlIGFncnVwYXI7IHBhcmEgZWxsbyBzZSB1dGlsaXphIGVsIGFyZ3VtZW50byBgbmAuIENvbiBlbCBjw7NkaWdvIHF1ZSBoYXkgYSBjb250aW51YWNpw7NuIGxlIHBpZGVzIGEgUiBxdWUgcXVpZXJlcyBleHRyYWVyIHRvZG9zIGxvcyBncnVwb3MgZGUgY3VhdHJvIHBhbGFicmFzICg0LWdyYW1zKSB5IHF1ZSBpbmZvcm1lIGRlIGxhIGZyZWN1ZW5jaWEgYWJzb2x1dGEgeSBxdWUgbGFzIHByZXNlbnRlIGVuIG9yZGVuIGRlY3JlY2llbnRlLiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0aWR5X3NvdHUgJT4lCiAgdW5uZXN0X3Rva2Vucyh3b3JkLAogICAgICAgICAgICAgICAgdGV4dCwKICAgICAgICAgICAgICAgIHRva2VuID0gIm5ncmFtcyIsCiAgICAgICAgICAgICAgICBuID0gNCkgJT4lCiAgY291bnQod29yZCwgc29ydD1UKQpgYGAKClRhbWJpw6luIGVzIHBvc2libGUgZGl2aWRpciBlbCB0ZXh0byBlbiBvcmFjaW9uZXMgKG5vIGVucmVkZW1vcyBjb24gbG9zIGNvbmVwdG9zIGRlIG9yYWNpw7NuLCBhcXXDrSBmdW5jaW9uYW4gZGUgb3RyYSBtYW5lcmEpIGVuIHZleiBkZSBlbiBwYWxhYnJhcy4gRGUgbnVldm8gZXMgZsOhY2lsIGRlIGhhY2VyIHBvcnF1ZSBlbCBhcmd1bWVudG8gYHRva2Vuc2AgZGUgbGEgZnVuY2nDs24gYHVubmVzdF90b2tlbnNgIHB1ZWRlIHRlbmVyIGVsIHZhbG9yIGBzZW50ZW5jZXNgLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGlkeV9zb3R1ICU+JQogIHVubmVzdF90b2tlbnMoc2VudGVuY2VzLAogICAgICAgICAgICAgICAgdGV4dCwKICAgICAgICAgICAgICAgIHRva2VuID0gInNlbnRlbmNlcyIpCmBgYAoKPGRpdiBjbGFzcz0id2FybmluZyIgc3R5bGU9J3BhZGRpbmc6MC4xZW07IGJhY2tncm91bmQtY29sb3I6IzFlODQ0OTsgY29sb3I6I2YyZjJmMic+CjxzcGFuPgo8cCBzdHlsZT0nbWFyZ2luLXRvcDoxZW07IHRleHQtYWxpZ246Y2VudGVyJz4KPGI+QVRFTkNJw5NOPC9iPjxicj5DdWFuZG8gc2UgbGUgcGlkZSBxdWUgZGl2aWRhIGN1YWxxdWllciB0ZXh0byBlbiBwYWxhYnJhcywgX24tZ3JhbXNfIHUgb3JhY2lvbmVzLCB1bmEgZGUgbGFzIGNvc2FzIHF1ZSBoYWNlIGVzIGNvbnZlcnRpciBsYXMgbWF5w7pzY3VsYXMgZW4gbWluw7pzY3VsYXMgZGUgbWFuZXJhIHF1ZSBfRmlyc3RfIGVuIF9GaXJzdCBMYWR5XyB5IGxhIGRlIGN1YWxxdWllciBjYXNvIGRlIF9maXJzdF8gcXVlIHB1ZWRhIGFwYXJlY2VyIGEgbG8gbGFyZ28gZGVsIHRleHRvIGxhcyBjdWVudGUgY29tbyBsYSBtaXNtYSBwYWxhYnJhIHRpcG8uIFPDqSBxdWUgZXN0byBlcyBwcm9ibGVtw6F0aWNvIGVuIGFsZ3Vub3MgY2Fzb3MsIHBlcm8gaGF5IGhlcnJhbWllbnRhcyBwYXJhIHNvbHVjaW9uYXJsby48YnI+PC9wPgo8cCBzdHlsZT0nbWFyZ2luLWxlZnQ6MWVtOyc+CjwvcD48L3NwYW4+CjwvZGl2Pgo8YnI+CgpZYSBoZW1vcyB2aXN0byBxdWUgZW4gZWwgZGlzY3Vyc28gZGUgQmlkZW4gZGUgMjAyMiBoYWLDrWEgMTczNSBwYWxhYnJhcyBkaWZlcmVudGVzIChwYWxhYnJhcyB0aXBvKSwgeSB2aW1vcyBxdWUgY2FkYSB1bmEgZGUgZWxsYSBhcGFyZWNlIHVuIG7Dum1lcm8gZGV0ZXJtaW5hZG9zIGRlIHZlY2VzIChfdGhlXyBhcGFyZWNlIDMwMCB2ZWNlcywgX2FuZF8gMjY0IHkgX3RvXyAyMzQpLCBwZXJvIG5vIHNhYmVzIGN1w6FudGFzIHNvbiBsYXMgcGFsYWJyYXMgcXVlIGNvbmZvcm1hbiBlbCBkaXNjdXJzby4gSGF5IGA2NTUzYCBwYWxhYnJhcyB0b2tlbi4gTGEgZm9ybWEgZGUgY2FsY3VsYXJsbyBlcyBzdW1hbmRvIHRvZG9zIGxvcyB2YWxvcmVzIGRlIGBuYCBkZWwgb2JqZXRvIGB0aWR5X3dvcmRzYCB5IHNlIGNvbnNpZ3VlIGNvbiBsYSBmdW5jacOzbiBgc3VtYC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzdW0odGlkeV93b3JkcyRuKQpgYGAKCgojIyBTdG9wd29yZHN7LX0KCkFsIGRpdmlkaXIgZWwgdGV4dG8gZW4gdG9rZW5zLCBoYXMgdmlzdG8gcXVlIHBhbGFicmFzIHRhbiB1c3VhbGVzIGNvbW8g4oCcdGhl4oCdLCDigJx0b+KAnSwg4oCcYW5k4oCdLCDigJxvZuKAnSB5IOKAnHdl4oCdIGVzdMOhbiBlbiBsbyBhbHRvIGRlIGxhIHRhYmxhLiBFc3RhcyBwYWxhYnJhcyBwb2NvIHB1ZWRlbiBkZWNpciBkZWwgY29udGVuaWRvIGRlbCBkaXNjdXJzbyAoZW4gb3Ryb3MgdGlwb3MgZGUgYW7DoWxpc2lzIGVzdGFzIHBhbGFicmFzIHNvbiBvcm8gbW9saWRvKS4gVmFzIGEgYm9ycmFybGFzLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRhdGEoc3RvcF93b3JkcykKdGlkeV93b3JkcyAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkcykKYGBgCgpBIHBlc2FyIGRlIGhhYmVyIGVsbWluYWRvIHRvZGFzIGxhcyBfcGFsYWJyYXMgZGUgZnVuY2nDs25fIG8gX3BhbGFicmFzIHZhY8OtYXNfIGhheSwgc2luIGVtYmFyZ28sIGFsZ3VuYXMgcGFsYWJyYXMgbXV5IGNvcnJpZW50ZXMgeSBjb250cmFjY2lvbmVzIGNvbW8g4oCcbGV0J3PigJ0sIOKAnHRoYXQnc+KAnSwg4oCcaXQnc+KAnSBvIOKAnHdlJ3Jl4oCdLiBRdWl6w6EgdGUgaW50ZXJlc2UgbG9jYWxpemFyIHRvZGFzIGFxdWVsbGFzIHBhbGFicmFzIHF1ZSBzZSB1c2FuIGNvbiBtdWNow61zaW1hIG3DoXMgZnJlY3VlbmNpYSBlbiBlc3RlIHRleHRvIGRlIGxvIHF1ZSBzZSB1dGlsaXphbiBlbiB1biBiYXN0byBjb3JwdXMgZGVsIGluZ2zDqXMuIFBhcmEgY29uc2VndWlybG8sIG5lY2VzaXRhcyB1biBkYXRhc2V0IHF1ZSByZWNvcGlsZSBlc3RhcyBmcmVjdWVuY2lhcy4gVW5vIG11eSBidWVubyBwYXJhIGVzbyBlcyBlbCBkZSBbUGV0ZXIgTm9ydmlnJ3NdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvcnRhdG1hbi9lbmdsaXNoLXdvcmQtZnJlcXVlbmN5KSBxdWUgc2UgYmFzYSBlbiBlbCBHb29nbGUgV2ViIFRyaWxsaW9uIFdvcmQgQ29ycHVzLCBxdWUgc2UgaGEgY29uc3RydWlkbyBjb24gdGV4dG9zIGV4dHJhaWRvcyBkZSBzaXRpb3Mgd2ViIGVuIGluZ2zDqXMuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kd29yZF9mcmVxdWVuY2llcyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Byb2dyYW1taW5naGlzdG9yaWFuL2pla3lsbC9naC1wYWdlcy9hc3NldHMvYmFzaWMtdGV4dC1wcm9jZXNzaW5nLWluLXIvd29yZF9mcmVxdWVuY3kuY3N2IikKaGVhZCh3b3JkX2ZyZXF1ZW5jaWVzKQpgYGAKCkxhIHByaW1lcmEgY29sdW1uYSBpbmRpY2EgbGEgbGVuZ3VhIGRlIHF1ZSBzZSB0cmF0YSAoZXMgc2llbXBlIOKAnGVu4oCdID0gaW5nbMOpcyksIGxhIHNlZ3VuZGEgcHJlc2VudGEgbGFzIHBhbGFicmFzIHkgbGEgdGVyY2VyYSBvZnJlY2UgZWwgcG9yY2VudGFqZSBkZSB1c28gZGUgY2FkYSBwYWxhYnJhIGVuIGVsIFRyaWxsaW9uIFdvcmQgQ29ycHVzLgoKUGFyYSBjb21iaW5hciB0b2RhcyBlc3RhcyBmcmVjdWVuY2lhcyBjb24gbGEgdGFibGEgZW4gbGEgcXVlIHRpZW5lcyBjb24gbG9zIGRhdG9zIGRlbCBkaXNjdXJzbyBkZSBCaWRlbiBkZSAyMDIyLCB1c2Fyw6FzIGxhIGZ1bmNpw7NuIGBpbm5lcl9qb2luYC4gZXN0YSBmdW5jacOzbiB0b21hIGRvcyB0YWJsYXMgKG8gZG9zIGRhdGFzZXQpIHkgbG9zIGNvbWJpbmEgZW4gdW5hIHNvbGEgZW4gdmlydHVkIGRlIGxhcyBjb2x1bW5hcyBxdWUgdGVuZ2FuIGVsIG1pc21vIG5vbWJyZTsgZW4gZXN0ZSBjYXNvIGxhIGNvbHVtbmEgY29tw7puIGVzIGxhIGxsYW1hZGEgYHdvcmRgLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnRpZHlfd29yZHMgJT4lCiAgaW5uZXJfam9pbih3b3JkX2ZyZXF1ZW5jaWVzKSAlPiUKICBmaWx0ZXIoZnJlcXVlbmN5IDwgMC4xKQpgYGAKRXN0YSBsaXN0YSB5YSBlbXBpZXphIGEgdGVuZXIgdW4gYXNwZWN0byBtZWpvci4gVMOpcm1pbm9zIGNvbW8g4oCcYW1lcmljYeKAnSwg4oCcYW1lcmljYW4ocynigJ0sIOKAnHllYXLigJ0sIOKAnHBlb3BsZeKAnSB5IOKAnHdvcmxk4oCdIGhhbiBlc2NhbGFkbyBoYXN0YSBsYSBwYXJ0ZSBzdXBlcmlvciBkZSBsYSB0YWJsYSB5IHlhIHBvZGVtb3MgZXNwZWN1bGFyIHF1ZSBhcGFyZWNlbiBjb24gbXVjaGFzIGZyZWN1ZW5jaWEgZW4gZGlzY3Vyc29zIGRlIHBvbMOtdGljb3MsIGNvbW8gZXMgZWwgY2FzbyBkZWwgU09UVSwgcGVybyBzdSBvY3VycmVuY2lhIGVzIHJlbGF0aXZhbWVudGUgbWVub3IgZW4gb3Ryb3MgZG9taW5pb3MuIFNpIGVzdGFibGVjaWVyYXMgdW4gdW1icmFsIG11Y2hvIG3DoXMgYmFqbywgZGlnYW1vcyBkZWwgMC4wMiwgcG9kcsOhcyB2ZXIgdW4gcmVzdW1lbiBtdWNobyBtw6FzIGludGVyZXNhbnRlIGRlbCBkaXNjdXJzby4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0aWR5X3dvcmRzICU+JQogIGlubmVyX2pvaW4od29yZF9mcmVxdWVuY2llcykgJT4lCiAgZmlsdGVyKGZyZXF1ZW5jeSA8IDAuMDIpCmBgYAoKU2kgaGFjZXMgY2xpYyBlbiBlbCBgTmV4dGAgZGUgbGEgcGFydGUgYmFqYSBkZSBsYSB0YWJsYSBhbnRlcmlvciwgdmVyw6FzIHF1ZSBhcGFyZWNlbiBhbGd1bm9zIHTDqXJtaW5vcyBpbnRlcmVzYW50ZXMgY29tbyDigJxwdXRpbuKAnSwg4oCcdWtyYWluaWFu4oCdLCDigJxhbGxpZXPigJ0sIOKAnHBhbmRlbWlj4oCdLCDigJxkZWZpY2l04oCdIGUg4oCcaW5mbGF0aW9u4oCdLiBFc3RvcyBwYXJlY2VuIHNlciBhbGd1bm9zIGRlIGxvcyB0ZW1hcyBkZWwgZGlzY3Vyc28uCgpBbnRlcyBkZSBtZXRlcnRlIGVuIGFndWEgbcOhcyBwcm9mdW5kYXMsIHZhcyBhIHZlciB1biBicmV2ZSByZXN1bWVuIGJhc2FkbyBlbiBsYXMgY2luY28gcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIGRlIGVzdGUgZGlzY3Vyc28uIHBhcmEgY29uc2VndWlybG8sIG5lY2VzaXRhcyBvdHJhIHRhYmxhIGNvbiB1bmEgc2VyaWUgZGUgbWV0YWRhdG9zIHF1ZSB0ZSBwZXJtaXRhbiBpZGVudGlmaWNhciB0b3RhbG1lbnRlIGNhZGEgdW5vIGRlIGxvcyBkaXNjdXJzb3MuIEVzdGEgdGFibGEgY29udGllbmUgZWwgbm9tYnJlIGRlbCBQcmVzaWRlbnRlLCBlbCBhw7FvIGRlbCBkaXNjdXJzbywgYcOxb3MgZW4gbG9zIHF1ZSBlc3R1dm8gZW4gbGEgQ2FzYSBCbGFuY2EgKGFsZ28gcmVhbG1lbnRlIGlycmVsZXZhbnRlKSwgcGFydGlkbyBhbCBxdWUgcGVydGVuZWNlIHkgdGlwbyBkZSBkaXNjdXJzbywgcHVlcyBwdWVkZW4gZXNjcml0b3MgbyBoYWJsYWRvcy4gVmFzIGEgY2FyZ2FybG8gZW4gdHUgb3JkZW5hZG9yLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNvdHVfbWV0YSA8LSByZWFkX3RzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzdQYXJ0aWRhc0RpZ2l0YWwvTUxleC9tYXN0ZXIvc290dV9tZXRhLnR4dCIpCiMgTXVlc3RyYSBlbCBjb250ZW5pZG8gKHNvbG8gZWwgcHJpbmNpcGlvKQpzb3R1X21ldGEKYGBgCgpZYSBlcyBob3JhIGRlIHJlc3VtaXJsbyBlbiB1bmEgc29sYSBsw61uZWEuIEFsIGZpbmFsIGRlIGxhIGluc3RydWNjacOzbiBoYXkgdW4gYDVgLCBsbyBjdWFsIGltcGxpY2EgcXVlIHNlbGVjY2lvbmFyw6EgbGFzIGNpbmNvIHBhbGFicmFzIG3DoXMgdXRpbGl6YWRhcywgc2UgcHVlZGUgaW5jcmVtZW50YXIgYSBsYSBjYW50aWRhZCBxdWUgcXVpZXJhcywgcGVybyB1biBzdW1hcmlvIGRlIG3DoXMgZGUgMTAgcGFsYWJyYXMgcXVpesOhIHNlYSBtdXkgcG9jbyBpbmZvcm1hdGl2bykuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3IgPSBGQUxTRX0KYWRkcmVzc19zdW1tYXJ5IDwtIHRpZHlfd29yZHMgJT4lCiAgaW5uZXJfam9pbih3b3JkX2ZyZXF1ZW5jaWVzKSAlPiUKICBmaWx0ZXIoZnJlcXVlbmN5IDwgMC4wMikKcmVzdWx0IDwtIGMoc290dV9tZXRhJHByZXNpZGVudFsyNDJdLAogICAgICAgICAgICBzb3R1X21ldGEkeWVhclsyNDJdLAogICAgICAgICAgICBhZGRyZXNzX3N1bW1hcnkkd29yZFsxOjVdKQpwYXN0ZShyZXN1bHQsIGNvbGxhcHNlID0gIjsgIikKYGBgCkNyZW8gcXVlIHNlIHBvZMOtYSBoYWJlciBlbGltaW5hZG8gbGEgcGFsYWJyYSAidG9uaWdodCIsIG5vIGRpY2UgbmFkYSBkZWwgdGVtYSwgdGFuIHNvbG8gcXVlIGxvIGxlecOzIHVuYSB0YXJkZSBub2NoZS4KCiMjIENvbmNvcmRhbmNpYXMgS1dJQ3stfQoKQ3JlYXIgdW5hcyBjb25jb3JkYW5jaWFzIG8gdGFibGFzIGRlIHBhbGFicmFzIGNsYXZlIGVuIGNvbnRleHRvIF9rZXktd29yZC1pbi1jb250ZXh0XyAoS1dJQykgZXMgdW5vIGRlIGxvcyBwcm9jZWRpbWllbnRvcyBtw6FzIHVzdWFsZXMgY3VhbmRvIHNlIGFuYWxpemFuIGRhdG9zIHRleHR1YWxlcy4gTm8gdGllbmVzIHF1ZSBlc2ZvcnphcnRlIG11Y2hvIHBhcmEgY29uc2VndWlybG8sIGhheSB2YXJpYXMgZnVuY2lvbmVzIHF1ZSBsbyBoYXLDoW4gY29uIHNlbmNpbGxlei4gVXRpbGl6YXLDoSBsYSBmdW5jacOzbiBga3dpY2AgZGUgbGEgbGlicmVyw61hIGBxdWFudGVkYWAgcGFyYSBjcmVhciB1bmEgY29uY29yZGFuY2lhIEtXSUMsIHBlcm8gZXhpZ2UgZGl2aWRpciBlbCB0ZXh0byBlbiB0b2tlbnMgY29uIGxhIGZ1bmNpw7NuIGB0b2tlbnNgLCBwZXJvIGRlIGxhIHByb3BpYSBsaWJyZXLDrWEgYHF1YW50ZWRhYC4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka3dpY19tdWx0aXBsZSA8LSBsYXN0X3NvdHUgJT4lCiAgdG9rZW5zKCkgJT4lCiAga3dpYyhwYXR0ZXJuID0gIlVrcmFpbmUiLAogICAgICAgd2luZG93ID0gMykgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCiMgSW5zcGVjY2lvbmEgZWwgY29taWVuem8gZGUgbGEgdGFibGEKaGVhZChrd2ljX211bHRpcGxlKQpgYGAKCiMgQW5hbGl6YXIgdG9kb3MgeSBjYWRhIHVubyBkZSBsb3MgRGlzY3Vyc29zIGRlbCBTdGF0ZSBvZiB0aGUgVW5pb24gZGVzZGUgMTc5MCBoYXN0YSAyMDI0CgpFbCBwcmltZXIgcGFzbyBwYXJhIGFuYWxpemFyIHRvZG9zIGVsIGNvcnB1cyBkZSBsb3MgZGlzY3Vyc29zIGRlbCBfU3RhdGUgb2YgdGhlIFVuaW9uXyBlcyBoYWNlciBxdWUgUiBsb3MgbGVhIHRvZG9zIGEgbGEgdmV6LiBFc3RvIGltcGxpY2EgdXRpbGl6YXIsIGNvbW8gaGFzIGhlY2hvIGFudGVzLCBsYSBmdW5jacOzbiBgcmVhZExpbmVzYCwgcGVybyB1bmlyw6FzIHRvZG9zIGxvcyBww6FycmFmb3MgZGUgY2FkYSB1bm8gZGUgbG9zIGRpc2N1cnNvcyBjb24gbGEgZnVuY2nDs24gYHBhc3RlYCwgYcOxYWRpcsOhcyB1biBzaXN0ZW1hIGRlIGlkZW50aWZpY2FjacOzbiBwYXJhIHNhYmVyIHF1w6kgZGlzY3Vyc28gZXMgY2FkYSB1bm8gZGUgZWxsb3MuIFRvZG8gZXN0byBzZSB0aWVuZSBxdWUgaGFjZXIgcG9yIG1lZGlvIGRlIHVuIGJ1Y2xlLCBwb3JxdWUgcXVpZXJlcyBtZXRlciBuYWRhIG3DoXMgeSBuYWRhIG1lbm9zIHF1ZSAyNDQgZGlzY3Vyc29zIGVuIHVuYSDDum5pY2EgdGFibGEgKHUgb2JqZXRvKSBsbGFtYWRhIGBhbGxfc290dWAuCgpQdWVzdG8gcXVlIGxvcyBmaWNoZXJvcyBzZSBlbmN1ZW50cmFuIGVuIGxhIHdlYiwgZWwgcHJpbWVyIHBhc28gZXMgZGVjbGFyYXIgbGEgdXJsIGNvbXBsZXRhIGRlIGNhZGEgZGlzY3Vyc28geSBjcmVhciB1bmEgdGFibGEgdmFjw61hIChgYWxsX3NvdHVgKSBwYXJhIGd1YXJkYXIgdG9kb3MgbG9zIHRlZXh0b3MuCgpMYSBpbnN0cnVjY2nDs24gcGFyYSBjcmVhciBsb3Mgbm9tYnJzIGRlIGxvcyBmaWNoZXJvcyBlcyB1biBwcm9jZXNvIHF1ZSByZXF1aWVyZSBkb3MgcGFzb3MuIEVuIHByaW1lciBsdWdhciwgc2UgaGEgZGUgZ3VhcmRhciAoYWxtYWNlbmFyKSBsYSBwYXJ0ZSBfZmlqYV8gZGUgbGEgVVJMIGVuIGVsIG9iamV0byBgYmFzZV91cmxgLiBZLCBlbiBzZWd1bmRvIGx1Z2FyLCBoYXkgcXVlIGNyZWFyIGxvcyBub21icmVzIGRlIGxvcyBmaWNoZXJvcyBxdWUgZXN0w6FuIGNvbnN0cnVpZG9zIHBvciB1biBuw7ptZXJvIGVudHJlIGAxYCB5IGAyNDRgLgpFc3TDoSBlcywgYXBhcmVudGVtZW50ZSwgbGEgcGFydGUgbcOhcyBjb21wbGljYWRpbGxhLCBwdWVzIGxhIHNlY3VlbmNpYSBkZSBgMWAgYSBgMjQ0YCBkZWJlIGVzdGFyIHByZWNlZGlkbyBwb3IgdW5vIChwYXJhIGxvcyBuw7ptZXJvcyBgMTBgIGEgYDk5YCkgbyBkb3MgKHBhcmEgbG9zIG7Dum1lcm9zIGVudHJlcyBlbCBgMWAgIHkgZWwgYDlgKSBjZXJvcy4gTGEgZnVuY2nDs24gaWRlYWwgcGFyYSBlc3RvIGVzIGBzcHJpbnRmYCwgcXVlIHBlcm1pdGUgdW5hIGbDs3JtdWxhIHBhcmEgYcOxYWRpciBsb3MgbsO6bWVyb3MgY29uIGxvcyBjZXJvcyBhIGxhIGl6cXVpZXJkYSwgZW4gbnVlc3RybyBjYXNvIGVudHJlIGAwMDFgIOKApiBgMDk5YOKApiBgMjQ0YCwgcXVlIGluY29ycG9yZSBsYSBleHRlbnNpw7NuIGAudHh0YCB5IHF1ZSBsYSBhw7FhZGEgYWwgZmluYWwgZGUgbGEgcGFydGUgZmlqYSBkZSBsYSB1cmwuIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJhc2VfdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vN1BhcnRpZGFzRGlnaXRhbC9NTGV4L21hc3RlciIKZmlsZXMgPC0gc3ByaW50ZigiJXMvc290dS8lMDNkLnR4dCIsIGJhc2VfdXJsLCAxOjI0NCkKYWxsX3NvdHUgPC0gTlVMTApgYGAKClF1aXrDoSB0ZSBlc3TDqXMgcHJlZ3VudGFkbyBxdcOpIGFzcGVjdG8gdGllbmVuIGxhcyBVUkwgcXVlIGFjYWJhcyBkZSBjcmVhci4gVmFzIGEgZWNoYXJsZSB1bmEgb2plYWRhIGEgbGFzIHNlaXMgcHJpbWVyYXMgY29uIGxhIGZ1bmNpw7NuIGBoZWFkYC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpoZWFkKGZpbGVzKQpgYGAKCnkgbGFzIHNlaXMgw7psdGltYXMgY29uIGxhIGZ1bmNpw7NuIGB0YWlsYC4KCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGFpbChmaWxlcykKYGBgCgpBaG9yYSB5YSBwdWVkZXMgbGVlciB0b2RvcyBsb3MgZGlzY3Vyc29zIGUgaW5jb3Jwb3JhcmxvcyBhIGxhIHRhYmxhIChsZSBsbGV2YXLDoSB1bm9zIGRvcyBtaW51dG9zIG8gdW4gcG9xdWl0byBtw6FzLikKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmb3IgKGkgaW4gMTpsZW5ndGgoZmlsZXMpKSB7CiAgc290dSA8LSByZWFkTGluZXMoZmlsZXNbaV0pCiAgc290dSA8LSBwYXN0ZShzb3R1LCBjb2xsYXBzZSA9ICJcbiIpCiAgdGVtcG9yYXJ5IDwtIHRpYmJsZShzcGVlY2ggPSBpLAogICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IHNvdHUpCiAgYWxsX3NvdHUgPC0gYmluZF9yb3dzKGFsbF9zb3R1LCB0ZW1wb3JhcnkpCn0KYGBgCgpBZGVtw6FzLCBsZSB2YWFzIGEgYcOxYWRpciBsb3MgbWV0YWRhdG9zIHF1ZSB0aWVuZXMgZ3VhcmRhZG9zIGVuIGBzb3R1X21ldGFgOgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF9zb3R1IDwtIGZ1bGxfam9pbihhbGxfc290dSwgc290dV9tZXRhLCBieT0ic3BlZWNoIikKYGBgCgoKIyMgQW7DoWxpc2lzIGV4cGxvcmF0b3JpbwoKeWEgdGllbmVzIHRvZG9zIGxvcyBlbGVtZW50b3MgcXVlIG5lY2VzaXRhcyBwYXJhIGFuYWxpemFyIHRvZG9zIGxvcyBkaXNjdXJzb3MgU09UVSBkZSBsb3Mgw7psdGltb3MgMjQ0IGHDsW9zLiBMbyBwcmltZXJvIGVzIGRpdmlkaXJsb3MgZW4gdG9rZW5zLiBZYSBoYXMgdmlzdG8gcXVlIMOxYSBmdW5jacOzbiAgYHVubmVzdF90b2tlbnNgIGxvcyBwdWVkZSBoYWNlciBlbiBwb3F1w61zaW1vIHRpZW1wby4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQphbGxfd29yZHMgPC0gYWxsX3NvdHUgJT4lCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KQphbGxfd29yZHMKYGBgCgpUaWVuZXMgYWwgYWxjYW5jZSBkZSBsb3MgZGVkb3MgdW4gY29ycHVzIGRlIHBvY28gbcOhcyBkZSAyLjAwMC4wMDAgZGUgcGFsYWJyYXMgKGV4YWN0YW1lbnRlIDIuMDE3LjUwNCB0b2tlbnMpLiBBaG9yYSBwdWVkZXMgcGxhbnRlYXJ0ZSBtdWNoYXMgcHJlZ3VudGFzIGNvbW8gwr9IYXkgdW4gcGF0csOzbiB0ZW1wb3JhbCBxdWUgcHVlZGEgZXhwbGljYXIgbGEgZGlmZXJlbnRlIGV4dGVuc2nDs24gZGUgY2FkYSBkaXNjdXJzbz8gwr9Dw7NtbyBzZSBjb21wYXJhIGxhIGV4dGVuc2nDs24gZGUgbG9zIGRpc2N1cnNvcyBkZSBsYXMgw7psdGltYXMgYWRtaW5pc3RyYWNpb25lcyBjb24gbG9zIGRlIEZyYW5rbGluIEQuIFJvb3NldmVsdCwgQWJyYWhhbSBMaW5jb2xuIHkgR2VvcmdlIFdhc2hpbmd0b24/CgpMYSBtZWpvciBtYW5lcmEgcGFyYSBvYnNlcnZhciBlc3RvIGVzIGNvbiB1biBncsOhZmljbyBkZSBkaXNwZXJzacOzbi4gU2UgcHVlZGUgZGlidWphciBjb24gbGEgZnVuY2nDs24gYGdncGxvdGAsIHBhcmEgZWxsbyBwb25kcsOhcyBlbCBhw7FvIGVuIGVsIGVqZSBYIChlbCBob3Jpem9udGFsKSB5IGxhIGxvbmdpdHVkLCBlbnRlbmRpZGEgY29tbyBlbCBuw7ptZXJvIGRlIHBhbGFicmFzIG8gdG9rZW5zICBgbmAgZW4gZWwgZWplIHZlcnRpY2FsIChvIGB5YCkuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQphbGxfd29yZHMgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgY291bnQoKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeWVhciwKICAgICAgICAgICAgICAgICBuKSkKYGBgCgpQYXJlY2Ugc2VyIHF1ZSBlbnRyZSAxNzkwIHkgMTg1MCBsYSBsb25naXR1ZCBkZSBsb3MgZGlzY3Vyc29zIGF1bWVudMOzIHByb2dyZXNpdmFtZW50ZSBsYSBsb25naXR1ZCBkZSBsb3MgZGlzY3Vyc29zLCBkdXJhbnRlIHVub3MgcG9jb3MgYW5vcyBkZWNyZWNpw7NuLCB5IHZvbHZpZXJvbiBhIGNyZWNhciBoYXN0YSBmaW5hbGVzIGRlbCBzaWdsbyBYSVguIExhIGV4dGVuc2nDs24gZGlzbWludXnDsyBkcsOhc3RpY2FtZW50ZSBlbiBsYSDDqXBvY2EgZGUgbGEgSSBHZXJyYSBNdW5kaWFsLCBhdW5xdWUgYSBsbyBsYXJnbyBkZWwgc2lnbG8gWFggaGF5IHVuYSBwdcOxYWRvIGRlIGRpc2N1cnNvcyBtdXkgZXh0ZW5zb3MuIEhhY2lhIDIwMDEgZGlzbWludXllcm9uIHkgY29tZW56YXJvbiBhIGFsYXJnYXJzZSBkZSBudWV2by4gwr9IYXkgYWxndW5hIHJhesOzbiBwYXJhIGVzdGUgdmFpdsOpbiBlbiBsYXMgbG9uZ2l0dWQgZGUgbG9zIGRpc2N1cnNvcy4gSGFzIHZpc3RvIHF1ZSBhbGd1bm9zIHByZXNpZGVudGVzIHByZWRlbnRhcm9uIHN1cyBkaXNjdXJzb3MgcG9yIGVzY3JpdG8gbWllbnRyYXMgcXVlIG90cm9zIGxvcyBsZXllcm9uLiDCv0VzIGVzdGEgbGEgcmF6w7NuPyBQdWVkZXMgYXZlcmlndWFybG8gZW4gc2VndW5kb3MuIFBhcmEgZWxsbyB2YXMgYSBlbXBsZWFyIGVsIGFyZ3VtZW50byBgY29sb3JgIHBhcmEgcXVlIGxvcyBjb2xvcmVlIGRlIGFjdWVyZG8gY29uIGVsIHZhbG9yIGRlIGxhIHZhcmlhYmxlIGBzb3R1X3R5cGVgLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsX3dvcmRzICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGNvdW50KCkgJT4lCiAgbGVmdF9qb2luKHNvdHVfbWV0YSkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHllYXIsCiAgICAgICAgICAgICAgICAgbiwKICAgICAgICAgICAgICAgICBjb2xvciA9IHNvdHVfdHlwZSkpCmBgYAoKUHVlcyBlc28gcGFyZWNlLiBFbCBpbmNyZW1lbnRvIHF1ZSBzZSBkYSBhIGxvIGxhcmdvIGRlbCBzaWdsbyBYSVggc2UgZGViZSBhIHF1ZSBzZSBwYXPDsyBkZSB1biBkaXNjdXJzbyBvcmFsIGEgb3RybyBlc2NyaXRvLiBFbCBjYW1iaW8gcmFkaWNhbCBzZSBwcm9kdWpvIGN1YW5kbyBjdWFuZG8gV29vZHJvdyBXaWxzb24gKGVuIDE5MTMpIHZvbHZpw7MgYWwgb3JpZ2VuLCB5IGxvIGhpem8gb3JhbG1lbnRlIGVuIGVsIENvbmdyZXNvIGRlIEVzdGFkb3MgVW5pZG9zLiBDb21vIHB1ZWRlcyB2ZXIsIGxvcyBfb3V0bGllcnNfIChsb3MgbcOhcyBsYXJnb3MpIHNlIHByZXNlbnRhcm9uIHRvZG9zIHBvciBlc2NyaXRvIHRyYXMgbGEgU2VndW5kYSBHdWVycmEgTXVuZGlhbC4KCk90cmEgcHJlZ3VudGEgcXVlIHRlIHB1ZWRlcyBoYWNlciBlcyBzaSBlbCBwZXJ0ZW5lY2VyIGEgdW5vIHUgb3RybyBwYXJ0aWRvIHRpZW5lIGFsZ3VuYSBpbmZsdWVuY2lhIGVuIGxhIGV4dGVuc2nDs24gZGUgbG9zIGRpc2N1cnNvcy4gTG8gcHVlZGVzIGF2ZXJpZ3VhciBlbiBzZWd1bmRvcyBjb24gdGFuIHNvbG8gY2FtYmlhciBjb24gY2FtYmlhciBxdWUgZWwgYXJndW1lbnRvIGBjb2xvcmAgdXNlIGBwYXJ0eWAgZW4gdmV6IGRlIGBzb3R1X3R5cGVgLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsX3dvcmRzICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGNvdW50KCkgJT4lCiAgbGVmdF9qb2luKHNvdHVfbWV0YSkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHllYXIsCiAgICAgICAgICAgICAgICAgbiwKICAgICAgICAgICAgICAgICBjb2xvciA9IHBhcnR5KSkKYGBgCgpObyBwYXJlY2UgcXVlIGVsIHNlciBkZSBkZSB1bm8gdSBvdHJvIHBhcnRpZG9zIHRlbmdhIG5hZGEgcXVlIHZlciBjb24gbGEgZXh0ZW5zacOzbi4gTG8gbcOhcyBjdXJpb3NvIGVzIHF1ZSBkZXNkZSBmaW5hbGVzIGRlIGxhIGTDqWNhZGEgZGUgMTk5MCBsb3MgZGlzY3Vyc29zIG3DoXMgZXh0ZW5zb3MgZnVlcm9uIGRlIHByZXNpc2RlbnRlcyBEZW3Ds2NyYXRhcy4KCkNvbW8gdmXDrXMsIGhhc3RhIGFob3JhIHNvbG8gbm9zIGhlbW9zIHBsYW50ZWFkbyBwcmVndW50YXMgcXVlIHNlIHBvZHLDrWFuIGhhY2VyIHVuIGVzdHVkaWFudGUgZGUgcG9sw610aWNhcyBvIGRlIGhpc3RvcmlhLCBwZXJvIHBvZGVtb3MgaGFjZXJub3Mgb3RyYXMgcXVlIGludGVyZXNlbiBhIG90cm9zIGVzcGVjaWFsaXN0YXMuCgpIYXMgdmlzdG8gcXVlIHB1ZWRlcyBkaXZpZGlyIGVsIHRleHRvIGVuIG9yYWNpb25lcy4gbGEgbG9uZ2l0dWQgZGUgZXN0YXMgcHVlZGUgc2VyIHVuIHBvc2libGUgcmFzZ28gZXN0aWxvLCBlIGluY2x1c28gcHVlZGUgaGFibGFyIGRlIGxhIGNvbXBsZWppZGFkIGRlbCB0ZXh0by4gQXPDrSBxdWUgdmFtb3MgYSBkaXZpZGlybG9zIGVuIG9yYWNpb25lcyB5IGNvbnRhciBjdcOhbnRhcyBwYWxhYnJhcyB0aWVuZSBjYWRhIHVubyBkZSBlbGxvcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF9zZW50ZW5jZXMgPC0gYWxsX3NvdHUgJT4lCiAgICB1bm5lc3RfdG9rZW5zKHNlbnRlbmNlLAogICAgICAgICAgICAgICAgICB0ZXh0LAogICAgICAgICAgICAgICAgICB0b2tlbiA9ICJzZW50ZW5jZXMiKSAlPiUKICBtdXRhdGUoTnVtYmVyV29yZHMgPSBzdHJfY291bnQoc2VudGVuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIlxcdysiKSkKYGBgCgpDYWxjdWxlbW9zIGxhIG1lZGlhbmEgeSB2YW1vcyBhIHZlcmxhIHVuIHVuYSBncsOhZmljYSwgZXMgbcOhcyBjbGFybyBxdWUgdW5hIHRhYmxhIGNvbiAyNDQgbMOtbmVhcyBkZSBuw7ptZXJvcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF9zZW50ZW5jZXMgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgbXV0YXRlKG1lZGlhbiA9IG1lZGlhbihOdW1iZXJXb3JkcykpICU+JQogIGxlZnRfam9pbihzb3R1X21ldGEpICU+JQogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh5ZWFyLAogICAgICAgICAgICAgICAgIG1lZGlhbikpCmBgYAoKTGEgZ3LDoWZpY2EgbXVlc3RyYSBxdWUsIGEgbG8gbGFyZ28gZGVsIHRpZW1wbywgaGF5IHVuYSB0ZW5kZW5jaWEgZ2VuZXJhbCBhIHVzYXIgb3JhY2lvbmVzIGNhZGEgdmV6IG3DoXMgY29ydGFzLiBSZWN1ZXJkYSBxdWUgYWxndW5vcyBkZSBsb3MgZGlzY3Vyc29zIHRyYXMgbGEgU2VndW5kYSBHdWVycmEgTXVuZGlhbCBmdWVyb24gZXNjcml0b3MsIGNvbW8gbGEgZ3JhbiBtYXlvcsOtYSBkZSBsb3MgZGVsIHNpZ2xvIFhJWC4gRXN0w6EgY2xhcm8gcXVlIGEgbWF5b3IgZXh0ZW5zacOzbiBkZWwgZGlzY3Vyc28sIGEgcGVzYXIgZGUgZXN0YXIgZXNjcml0bywgbmFkYSB0aWVuZSBxdWUgdmVyIGNvbiBsYSBsb25naXR1ZCBkZSBsYXMgb3JhY2lvbmVzLiBFcyBldmlkZW50ZSBxdWUgbG9zIGRpc2N1cnNvcyBkZSBsb3MgcHJlc2lkZW50ZXMgbm9ydGFtZXJpY2Fub3MgaGFuIHRlbmRpZG8gYSB1c2FyIG9yYWNpb25lcyBjYWRhIHZleiBtw6FzIGNvcnRhcy4KClBhcmEgdmVybG8gZGUgdW5hIG1hbmVyYSBtw6FzIGV2aWRlbnRlLCBlcyBwb3NpYmxlIGluY29ycG9yYXIgdW5hIGzDrW5lYSBxdWUgbWFycXVlIGxhIHRlbmRlbmNpYSBjb24gbGEgZnVuY2nDs24gYGdlb21fc21vb3RoYC4gRXN0ZSB0aXBvIGRlIGzDrW5lYSBzb24gZGUgZ3JhbiBheXVkYSBlbiBtdWNoYXMgZ3LDoWZpY2FzLiBUaWVuZW4gdW4gZG9ibGUgcHJvw7NzaXRvOiBtYXJjYW4gbGEgdGVuZGVuY2lhIGdlbmVyYWwgYSBsbyBsYXJnbyBkZSB1bmEgc2VyaWUgdGVtcG9yYWwgeSBtdWVzdHJhbiBjb24gbWF5b3IgY2xhcmlkYWQgbG9zIGRhdG9zIGF0w61waWNvcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF9zZW50ZW5jZXMgJT4lCiAgICBncm91cF9ieSh5ZWFyKSAlPiUKICAgIG11dGF0ZShtZWRpYW4gPSBtZWRpYW4oTnVtYmVyV29yZHMpKSAlPiUKICAgIGxlZnRfam9pbihzb3R1X21ldGEpICU+JQogICAgZ2dwbG90KGFlcyh4ID0geWVhciwKICAgICAgICAgICAgICAgeSA9IG1lZGlhbikpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpCmBgYAoKwqFBVklTTyEgRXMgY2llcnRvIHF1ZSBoYXkgdW5hIHRlbmRlbmNpYSBnZW5lcmFsIGEgYWNvcnRhciBsYXMgb3JhY2lvbmVzLCBwZXJvIGhheSB1biBwZXF1ZcOxbyBwcm9ibGVtYSBxdWUgbm8gaGUgY29uc2lkZXJhZG8geSBxdWUgcG9kcsOtYSB0ZW5lciBjaWVydGEgaW5mbHVlbmNpYSBlbiBsb3MgcmVzdWx0YWRvcyBmaW5hbGVzLCBwb3IgbG8gcXVlIGRlYsOpaXMgdGVuZXJsbyBlbiBjdWVudGEuIEVsIHRvcXVlbml6YWRvciwgZXMgZGVjaXIsIGxhcyBpbnN0cnVjY2lvbmVzIHF1ZSBkaXZpZGVuIGVsIHRleHRvIGVuIG9yYWNpb25lcyBjb25zaWRlcmEgcXVlIGVsIGzDrW1pdGUgZGUgbGFzIG9yYWNpb25lcyBzb24gdW5vIGRlIGVzdG9zIHRyZXMgY2Fzb3M6IHB1bnRvcywgc2lnbm8gZGUgYWRtaXJhY2nDs24geSBzaWdub3MgZGUgaW50ZXJyb2dhY2nDs24uIEEgbG8gbGFyZ28gZGUgZXN0b3MgdGV4dG9zIGhheSB1bmFzIGN1YW50YXMgYWJyZXZpYXR1cmFzLCBjb21vIF9Nci5fLCBjb21vIHNlIHB1ZWRlIHZlciBlbiBlc3RhIHRhYmxhLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsX3dvcmRzICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHdvcmQsIHJlZ2V4KCJcXGJtclxcYiIsIGlnbm9yZV9jYXNlID0gVFJVRSkpKSAlPiUKICBzZWxlY3Qod29yZCkKYGBgCgojIyBMYXMgcGFsYWJyYXMgbcOhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGNhZGEgcHJlc2lkZW50ZSAoUGFsYWJyYXMgY2xhdmUpey19CgoKSGFzIHZpc3RvIGxvcyBiaWdyYW1hcyBtw6FzIGZyZWN1ZW50ZXMgZGUgY2FkYSB1bm8gZGUgbG9zIHByZXNpZGVudGVzLCBwZXJvIG5vIHNvbiBuYWRhIGlsdXN0cmF0aXZvcywgcHVlcyBubyBvZnJlY2VuIGxhcyBwYWxhYnJhcyBtw6FzIGludGVyZXNhbnRlcyBkZSBjYWRhIHVubyBkZSBlbGxvcy4KClZhcyBhIGFuYWxpemFyIGxvcyBkaXNjdXJzb3MgZGUgY2FkYSBwcmVzaWRlbnRlLCBwZXJvIG5vIGRlc2RlIGVsIHB1bnRvIGRlIHZpc3RhIGRlIGxvcyBkaXNjcnVzb3MgaW5kaXZpZHVhbGVzLCBzaW5vIGRlbCBjb25qdW50byBkZSBjYWRhIHVubyBkZSBlbGxvcy5MYSB0YWxiYSBxdWUgaGF5IGEgY29udGludWFjacOzbiB0ZSBtb3N0cmFyw6EgcXVlIGxhIHBhbGFicmEgbcOhcyBmcmVjdWVudGUgZGUgUm9vc2V2ZWx0IGVzIGVsIGFydMOtY3VsbyDigJx0aGXigJ0geSBsYSBzZWd1bmEgbcOhcyB1dGlsaXphZGEgZXMgbGEgcHJlcG9zaWNpw7NuIOKAnG9m4oCdLiBTb24gdGFuIGNvcnJpZW50ZXMgcXVlIG5vIHRpZW5lbiBuaW5nw7puIGludGVyw6lzLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsX3dvcmRzX2J5X3ByZXNpZGVudCA8LSBhbGxfc290dSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGNvdW50KHByZXNpZGVudCwgd29yZCwgc29ydCA9IFQpCgojIGluc3BlY3QgZGF0YSAodG9wIDEwIHNvcnRlZCkKCmFsbF93b3Jkc19ieV9wcmVzaWRlbnQKYGBgCgpTaW4gZW1iYXJnbywgaGF5IHVuYSBtYW5lcmEgZGUgbG9jYWxpemFyIGxhcyBwYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIGRlIGNhZGEgc3ViY29ycHVzIChzZXJpZSBkZSBkaXNjdXJzb3MgcHJvbnVuY2lhZG9zIHBvciBjYWRhIHByZXNpZGVudGUpIHkgc2UgY29uc2lndWUgY29tcGFyw6FuZG9sb3MgZW50cmUgc8OtLiBMYXMgbcOhdGVtw6F0aWNhcyBxdWUgaGF5IHRyYXMgZXN0byBzb24gZGUgaW5mYXJ0bywgcGVybyB1bmEgZGUgbGFzIGNvc2FzIGFkbWlyYWJsZXMgZGUgUiB5IGRlIHRvZGFzIGxhcyBsaWJyZXLDrWEgcXVlIGV4aXN0ZW4gZXMgcXVlIGhhIGhhYmlkbyBxdWllbmVzIHNlIGhhbiBvY3VwYWRvIGRlIG9mcmVjZXJub3MgZnVuY2lvbmVzIHNlbmNpbGxhcyBkZSB1c2FyLiBVbm8gZGUgbG9zIG3DqXRvZG9zIHBhcmEgZXh0cmFlciBsYXMgcGFsYWJyYXMgbcOxYXMgaW1wb3J0YW50ZXMgbyBjYXJhY3RlcsOtc3RpY2EgZGUgY2FkYSBwcmVzaWRlbnRlIChvIGRlIGN1YWxxdWllciBvdHJvIHRleHRvKSBlcyBlbCBsbGFtYWRvIFRlcm0gRnJlcXVlbmN54oCTSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3ksIG8gdGYtaWRmIHBhcmEgYWJyZXZpYXIuIEVzIHVuYSBhbsOhbGlzaXMgZXN0YWTDrXN0aWNvIHF1ZSBjYWxjdWxhIGN1w6FsZXMgc29uIGxhcyBwYWxhYnJhcyBjbGF2ZSBkZSB1biB0ZXh0byB5LCBwb3IgbG90YW50bywgcmVmbGVqYW4gY3XDoW4gY2FyYWN0ZXLDrXN0aWNhcyBzb24gZGVudHJvIGRlIHVuIHRleHRvIGRhZG8uIEVsIFRlcm0gRnJlcXVlbmN54oCTSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kgY2FsY3VsYSBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgcGFsYWJyYSBkZSB1biB0ZXh0byB5IGxhIGNvbXBhcmEgY29uIGxhIGZyZWN1ZW5jaWEgcXVlIG9mcmVjZSBlbiBsb3MgZG9jdW1lbnRvcyBlbiBsb3MgcXVlIGFwYXJlY2UuIGxhIGV4cGxpY2FjacOzbiBubyBlcyBtdXkgY2xhcmEsIHBlcm8gY8OzbW8gaGFsbGFyIGxvcyB2YWxvcmVzLCB5IHBvciB0YW50byBsYXMgcGFsYWJyYXMsIGVzIGFsZ28gc2VuY2lsbG8gZ3JhY2lhcyBhIGxhIGZ1bmNpw7NuIGBiaW5kX3RmX2lkZmAuCgpgYGB7ciBldmFsID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF93b3Jkc19ieV9wcmVzaWRlbnQgJT4lCiAgYmluZF90Zl9pZGYod29yZCwgcHJlc2lkZW50LCBuKSAlPiUKICBtdXRhdGUocHJlc2lkZW50ID0gZmFjdG9yKHByZXNpZGVudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkdlb3JnZSBXYXNoaW5ndG9uIiwgIkpvaG4gQWRhbXMiLCAiVGhvbWFzIEplZmZlcnNvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJKYW1lcyBNYWRpc29uIiwgIkphbWVzIE1vbnJvZSIsICJKb2huIFF1aW5jeSBBZGFtcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBbmRyZXcgSmFja3NvbiIsICJNYXJ0aW4gVmFuIEJ1cmVuIiwgIkpvaG4gVHlsZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSmFtZXMgSy4gUG9sayIsICJaYWNoYXJ5IFRheWxvciIsICJNaWxsYXJkIEZpbGxtb3JlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZyYW5rbGluIFBpZXJjZSIsICJKYW1lcyBCdWNoYW5hbiIsICJBYnJhaGFtIExpbmNvbG4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQW5kcmV3IEpvaG5zb24iLCAiVWx5c3NlcyBTLiBHcmFudCIsICJSdXRoZXJmb3JkIEIuIEhheWVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNoZXN0ZXIgQS4gQXJ0aHVyIiwgIkdyb3ZlciBDbGV2ZWxhbmQiLCAiQmVuamFtaW4gSGFycmlzb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV2lsbGlhbSBNY0tpbmxleSIsICJUaGVvZG9yZSBSb29zZXZlbHQiLCAiV2lsbGlhbSBIb3dhcmQgVGFmdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJXb29kcm93IFdpbHNvbiIsICJXYXJyZW4gRy4gSGFyZGluZyIsICJDYWx2aW4gQ29vbGlkZ2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSGVyYmVydCBIb292ZXIiLCAiRnJhbmtsaW4gRC4gUm9vc2V2ZWx0IiwgIkhhcnJ5IFMgVHJ1bWFuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkR3aWdodCBELiBFaXNlbmhvd2VyIiwgIkpvaG4gRi4gS2VubmVkeSIsICJMeW5kb24gQi4gSm9obnNvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSaWNoYXJkIE0uIE5peG9uIiwgIkdlcmFsZCBSLiBGb3JkIiwgIkppbW15IENhcnRlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSb25hbGQgUmVhZ2FuIiwgIkdlb3JnZSBCdXNoIiwgIldpbGxpYW0gSi4gQ2xpbnRvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHZW9yZ2UgVy4gQnVzaCIsICJCYXJhY2sgT2JhbWEiLCAiRG9uYWxkIFRydW1wIiwgIkpvZSBCaWRlbiIpKSkgJT4lCiAgZ3JvdXBfYnkocHJlc2lkZW50KSAlPiUgCiAgc2xpY2VfbWF4KHRmX2lkZiwgbiA9IDEwKSAlPiUgCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCB0Zl9pZGYpKSAlPiUKICBnZ3Bsb3QoYWVzKHRmX2lkZiwgd29yZCwgZmlsbCA9IHByZXNpZGVudCkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgbGFicyh4ID0gInRmLWlkZiIsIHkgPSBOVUxMKSArCiAgZmFjZXRfd3JhcCh+cHJlc2lkZW50LCBuY29sID0gNCwgc2NhbGVzID0gImZyZWUiKQpgYGAKYGBge3IgZWNobz1GLCBmaWcuY2FwPSIiLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249ImNlbnRlciIsIG91dC53aWR0aD0nMTAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS83UGFydGlkYXNEaWdpdGFsL01MZXgvbWFzdGVyL2ltYWdlL01MZXhfMDEwLnBuZyIpCmBgYAoKSGF5IGRlbWFzaWFkb3MgY2lmcmFzLCBwdWVkZW4gcXVlIHRlbmdhbiBhbGfDum4gc2lnbmlmaWNhZG8sIHBlcm8gc29uIHVuIHNpbnNlbnRpZG8gcGFyYSBub3NvdHJvcy4gQXPDrSBxdWUgYm9ycsOpbW9zbG9zLiBQYXJhIGhhY2VybG8gaGF5IHF1ZSByZWhhY2VyIHBhcnRlIGRlbCB0cmFiYWpvLiBIYXkgcXVlIGRpdmlkaXIgZW4gcGFsYWJyYXMgdG9kb3MgbG9zIGRpY3Vyc29zLCBwZXJvIGxvIHByaW1lcm8gcXVlIGhheSBxdWUgaGFjZXIgZXMgYm9ycmFyIHRvZG9zIGxvcyBuw7ptZXJvcyBjb24gbGEgZnVuY2nDs24gYG11dGF0ZWAuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQphbGxfd29yZHNfYnlfcHJlc2lkZW50IDwtIGFsbF9zb3R1ICU+JQogIG11dGF0ZSh0ZXh0ID0gc3RyX3JlbW92ZV9hbGwodGV4dCwgIls6ZGlnaXQ6XSIpKSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGNvdW50KHByZXNpZGVudCwgd29yZCwgc29ydCA9IFQpCmBgYAoKVW5hIHZleiBoZWNobywgaGF5IHF1ZSByZWRpYnVqYXIgZWwgZ3LDoWZpY28uIE5vIGhheSBxdWUgaGFjZXIgbmFkYSBudWV2by4KCmBgYHtyIGV2YWwgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsX3dvcmRzX2J5X3ByZXNpZGVudCAlPiUKICBiaW5kX3RmX2lkZih3b3JkLCBwcmVzaWRlbnQsIG4pICU+JQogIG11dGF0ZShwcmVzaWRlbnQgPSBmYWN0b3IocHJlc2lkZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiR2VvcmdlIFdhc2hpbmd0b24iLCAiSm9obiBBZGFtcyIsICJUaG9tYXMgSmVmZmVyc29uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkphbWVzIE1hZGlzb24iLCAiSmFtZXMgTW9ucm9lIiwgIkpvaG4gUXVpbmN5IEFkYW1zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFuZHJldyBKYWNrc29uIiwgIk1hcnRpbiBWYW4gQnVyZW4iLCAiSm9obiBUeWxlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJKYW1lcyBLLiBQb2xrIiwgIlphY2hhcnkgVGF5bG9yIiwgIk1pbGxhcmQgRmlsbG1vcmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRnJhbmtsaW4gUGllcmNlIiwgIkphbWVzIEJ1Y2hhbmFuIiwgIkFicmFoYW0gTGluY29sbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBbmRyZXcgSm9obnNvbiIsICJVbHlzc2VzIFMuIEdyYW50IiwgIlJ1dGhlcmZvcmQgQi4gSGF5ZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2hlc3RlciBBLiBBcnRodXIiLCAiR3JvdmVyIENsZXZlbGFuZCIsICJCZW5qYW1pbiBIYXJyaXNvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJXaWxsaWFtIE1jS2lubGV5IiwgIlRoZW9kb3JlIFJvb3NldmVsdCIsICJXaWxsaWFtIEhvd2FyZCBUYWZ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIldvb2Ryb3cgV2lsc29uIiwgIldhcnJlbiBHLiBIYXJkaW5nIiwgIkNhbHZpbiBDb29saWRnZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJIZXJiZXJ0IEhvb3ZlciIsICJGcmFua2xpbiBELiBSb29zZXZlbHQiLCAiSGFycnkgUyBUcnVtYW4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRHdpZ2h0IEQuIEVpc2VuaG93ZXIiLCAiSm9obiBGLiBLZW5uZWR5IiwgIkx5bmRvbiBCLiBKb2huc29uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJpY2hhcmQgTS4gTml4b24iLCAiR2VyYWxkIFIuIEZvcmQiLCAiSmltbXkgQ2FydGVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJvbmFsZCBSZWFnYW4iLCAiR2VvcmdlIEJ1c2giLCAiV2lsbGlhbSBKLiBDbGludG9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdlb3JnZSBXLiBCdXNoIiwgIkJhcmFjayBPYmFtYSIsICJEb25hbGQgVHJ1bXAiLCAiSm9lIEJpZGVuIikpKSAlPiUKICBncm91cF9ieShwcmVzaWRlbnQpICU+JSAKICBzbGljZV9tYXgodGZfaWRmLCBuID0gMTApICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIHRmX2lkZikpICU+JQogIGdncGxvdChhZXModGZfaWRmLCB3b3JkLCBmaWxsID0gcHJlc2lkZW50KSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSAidGYtaWRmIiwgeSA9IE5VTEwpICsKICBmYWNldF93cmFwKH5wcmVzaWRlbnQsIG5jb2wgPSA0LCBzY2FsZXMgPSAiZnJlZSIpCmBgYApgYGB7ciBlY2hvPUYsIGZpZy5jYXA9IiIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0iY2VudGVyIiwgb3V0LndpZHRoPScxMCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzdQYXJ0aWRhc0RpZ2l0YWwvTUxleC9tYXN0ZXIvaW1hZ2UvTUxleF8wMTEucG5nIikKYGBgCkFob3JhIGxvcyBncsOhZmljb3MgdGllbmVuIGFsZ28gbcOhcyBkZSBzZW50aWRvIHkgZGVwZW5kZSBkZSB0aSBsYSBpbnRlcnByZXRhY2nDs24uIEhheSB1bmEgc2VyaWUgZGUgcHJvYmxlbWFzIHF1ZSBkZWJlcyB0ZW5lciBlbiBjdWVudGEgLiBTaSB0ZSBmaWphcyBlbiBlbCBncsOhZmljbyBkZSAgS2VubmVkeSB2ZXLDoXMgcXVlIGFwYXJlY2VuIOKAnHZpZXTigJ0geSDigJxuYW3igJ0sIHBlcm8gc29uIHVuYSBzb2xhIHBhbGFicmEg4oCcdmlldC1uYW3igJ0uIExvIG1pc21vIHN1Y2VkZSBlbiBlbCBkZSBCdXNoIEpyLiDigJxhbC1RYWlkYeKAnSBzZSBoYSBkaXZpZGlkbyBlbiBsb3Mgc2VnbWVudG9zIOKAnGFs4oCdIHkg4oCccWFpZGHigJ0uIFBvciBsbyB0YW50bywgY3VhbmRvIHVzZXMgZXN0YXMgdMOpY25pY2FzIHBhcmEgbWluYXIgdGV4dG9zLCB0aWVuZXMgcXVlIHRlbmVyIGVuIGN1ZW50YSBsb3MgcHJvYmxlbWFzIHF1ZSBwdWVkZSBoYWJlciBlc2NvbmRpZG9zIGVuIGxvcyB0ZXh0b3MuCgojIyBDb2xvY2FjaW9uZXN7LX0KClNlZ8O6biBBbHRlbmJlcmcgKDE5OTE6IDEyOCksIOKAnEFwcm94aW1hZGFtZW50ZSBlbCA3MCUgZGUgbGFzIHBhbGFicmFzIHF1ZSBjb25zdGl0dXllbiB1biBjb3JwdXMgZm9ybWFuIHBhcnRlIGRlIGNvbWJpbmFjaW9uZXMgZGUgcGFsYWJyYXMgdXN1YWxlc+KAnS4gTGEgaW52ZXN0aWdhY2nDs24gZGUgdGFsZXMgY29tYmluYWNpb25lcyBkZSBwYWxhYnJhcyBlbiBjb3JwdXMgc2UgcmVtb250YSBhIGxvcyBwcmltZXJvcyBlc3R1ZGlvcyBkZSBGaXJ0aCAoMTk1Nykgc29icmUgY29sb2NhY2lvbmVzLCBxdWllbiByZXN1bWnDsyBlc3RlIHByaW5jaXBpbyBjb24gdW5hIHN1Y2ludGEgZnJhc2U6IOKAnHJlY29ub2NlcsOhcyB1bmEgcGFsYWJyYSBwb3IgbGEgY29tcGHDscOtYSBxdWUgdGllbmXigJ0uCgoKRW4gZXN0YSBzZWNjacOzbiB2YXMgYSB2ZXIgdG9kYXMgbGFzIGNvbG9jYWNpb25lcyBxdWUgb2ZyZWNlbiBsb3MgZGlzY3Vyc29zIHByZXNpZGVuY2lhbGVzLiBZYSBoYXMgdmlzdG8gcXVlIHNlIHB1ZWRlIGRpdmlkaXIgdW4gdGV4dG9zIGVuIHRva2VucywgcGVybyB0YW1iacOpbiBlbiBfbi1ncmFtc18sIGVzIGRlY2lyLCBzZWN1ZW5jaWFzIGRlIG4tcGFsYWJyYXMgbyBuLXRva2VucykuIERpdmlkYW1vcyB0b2RvcyBsb3MgZGlzY3Vyc29zIGVuIGJpZ3JhbWFzLCBlbiBzZWN1ZW5jaWFzIGRlIGRvcyBwYWxhYnJhcyAvIHRva2Vucy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsbF9iaWdyYW1zIDwtIGFsbF9zb3R1ICU+JQogIHVubmVzdF90b2tlbnMoYmlncmFtLAogICAgICAgICAgICAgICAgdGV4dCwKICAgICAgICAgICAgICAgIHRva2VuID0gIm5ncmFtcyIsCiAgICAgICAgICAgICAgICBuID0gMikKCiMgaW5zcGVjdCBkYXRhICh0b3AgMTAgc29ydGVkKQoKYWxsX2JpZ3JhbXMgJT4lCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVCkKYGBgCgpDb21vIHlhIGhhcyB2aXN0bywgZWwgcmVzdWx0YWRvIG5vIGVzIG11eSBpbHVzdHJhdGl2by4gRGUgbnVldm8gc29uIGxhcyBwYWxhYnJhcyBncmFtYXRpY2FsZXMg4oCUcHJlcG9zaWNpb25lcywgYXJ0w61jdWxvcywgY29uanVuY2lvbmVz4oCm4oCUIGxhcyBxdWUgYXBhcmVjZW4gZW4gcHJpbWVyIGx1Z2FyLiBMYSDDum5pY2EgZXhjZXBjacOzbiwgZXNwZXJhYmxlLCBlcyBfVW5pdGVkIFN0YXRlc18uIFBvZHLDrWFtb3MgZXN0YXIgdGVudGFkb3MgZGUgdXNhciBsYSBmdW5jacOzbiBgYW50aV9qb2luKClgIHBhcmEgYm9ycmFybGFzLiBTaW4gZW1iYXJnbywgbm8gbG8gcHVlZGVzIGhhY2VyIGRpcmVjdGFtZW50ZSBwdWVzdG8gcXVlIGxhIGxpc3RhIGRlIHBhbGFicmFzIHZhY8OtYXMsIG8gX3N0b3B3b3Jkc18gcXVlIGhhcyBjYXJnYWRvIHNvbG8gY29udGllbmVuIHVuaWdyYW1hcywgbm8gaGF5IG5pIHVuIHNvbG8gYmlncmFtYXMsIHBvciBsbyBxdWUgbm8gZnVuY2lvbmFyw61hLgoKTGEgc29sdWNpw7NuIHNlLCB1bmEgdmV6IGF1ZSB0ZW5lbW9zIGxvcyBiaWdyYW1hcywgc2VwYXJhciBsb3MgZG9zIGNvbnN0aXR1eWVudGVzIHkgYm9ycmFyIGxhcyBwYWxhYnJhYXMgdmFjw61hcy4gQXVucXVlIHNlIHB1ZWRlIGhhY2VyIHRvZG8gY29uIHVuIHNvbG8gYmxvcXVlIGRlIGPDs2RpZ28sIGxvIHZveSBhIHByZXNlbnRhciBlbiB2YXJpb3MgcGFzb3MsIHBhcmEgcXVlIHB1ZWRhcyB2ZXIgbGEgbMOzZ2ljYSBkZWwgcHJvY2VkaW1pZW50by4KCkVuIHByaW1lciBsdWdhciB2YXNhIHNlcGFyYXIgbG9zIGRvcyBlbGVtZW50b3MgY29uIGxhIGZ1bmNpw7NuIGBzZXBhcmF0ZSgpYCB5IGxvcyB2YXMgYSBjb25zZXJ2YXIgZW4gZG9zIGNhbG91bW5hcyBxdWUgbGxhbWFyZW1vcyBgd29yZDFgIHkgYHdvcmQyYC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNlcGFyYXRlZF9iaWdyYW1zIDwtIGFsbF9iaWdyYW1zICU+JQogIHNlcGFyYXRlKGJpZ3JhbSwKICAgICAgICAgICAgYygid29yZDEiLCAid29yZDIiKSwKICAgICAgICAgICAgc2VwID0gIiAiKQoKIyBpbnNwZWN0IGRhdGEgKHRvcCAxMCBzb3J0ZWQpCgpzZXBhcmF0ZWRfYmlncmFtcyAlPiUKICBjb3VudCh3b3JkMSwgd29yZDIsIHNvcnQgPSBUKQpgYGAKClRpZW5lIGNhc2kgZWwgbWlzbW8gYXNwZWN0byBxdWUgbGEgdGFibGEgcXVlIHNlIGltcHJpbWnDsyBjdWFuZG8gZGl2aWRpbW9zIGxvcyB0ZXh0byBlbiBiaW1hZ3JhcywgcGVybyBjYWRhIHVuYSBkZSBsYXMgcGFydGVzIGVzdMOhIGVuIHVuYSBjb2x1bW5hICh2YXJpYWJsZSkgZGlmZXJlbnRlLgoKRWwgc2lndWllbnRlIHBhc28gZXMgYm9ycmFyIHRvZGFzIGxhcyBwYWxhYnJhcyB2YWPDrWFzLCBwZXJvIG5vIHB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBhbnRpX2pvaW5gLiBWYXMgYSBleHRyYWVyIHRvZGFzIGxhcyBwYWxhYnJhcyB2YWPDrWFzIGNvbiBgJWluJWAsIHRhbnRvIGRlIGxhIGNvbHVtbmEgYHdvcmQxYCBjb21vIGRlIGB3b3JkMmAsIHkgcXVlIG5vIHNlIGVuY3VlbnRyZW4gbGEgY29sdW1uYSDigJNgIWDigJMgZW4gbGEgY29sdW1uYSBgd29yZGAgZGxlIG9iamV0byBgc3RvcF93b3Jkc2AuIGVzIHVuIGdhbGltYXTDrWFzLCBsbyBzw6kuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWx0ZXJlZF9iaWdyYW1zIDwtIHNlcGFyYXRlZF9iaWdyYW1zICU+JQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQsCiAgICAgICAgICF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCkKCiMgaW5zcGVjdCBkYXRhICh0b3AgMTAgc29ydGVkKQoKZmlsdGVyZWRfYmlncmFtcyAlPiUKICBjb3VudCh3b3JkMSwgd29yZDIsIHNvcnQgPSBUKQpgYGAKCkF1bnF1ZSB0ZSBsbyBwdWVkYSBwYXJlY2VyLCBubyBlc3TDoXMgdmllbmRvIGJpZ3JhbWFzLCB0YW4gc29sbyBlc3TDoXMgdmllbmRvIGRvcyBwYWxhYnJhcyBjb250aWd1YXMgZW4gZG9zIGNvbHVtYW5hcyBkaWZlcmVudGVzLiBIYXkgcXVlIHJlY29uc3RydWlyIGxvcyBiaW1hZ3JhcyB5IHNlIGNvbnNpZ3VlIGNvbiBsYSBmdW5jacOzbiBgdW5pdGUoKWAsIHF1ZSByZXVuZSBlbiB1bmEgc29sYSBjb2x1bW5hIGxvcyB2YWxvcmVzIHF1ZSBoYXlhIGVuIGRvcyBvIG3DoXMgY29sdW1uYXMuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp1bml0ZWRfYmlncmFtcyA8LSBmaWx0ZXJlZF9iaWdyYW1zICU+JQogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpCgojIGluc3BlY3QgZGF0YSAodG9wIDEwIHNvcnRlZCkKCnVuaXRlZF9iaWdyYW1zICU+JQogIGNvdW50KGJpZ3JhbSwgc29ydCA9IFQpCmBgYAoKQWhvcmEgcG9kZW1vcyB0cmF6YXIgdW4gZ3LDoWZpY28gcXVlIG5vcyBwZXJtaXRhIHZlciBxdcOpIGJpZ3JhbWFzIHV0aWxpemEgY29uIG1heW9yIGZyZWN1ZW5jaWEgY2FkYSBwcmVzaWRlbnRlLiBMZSBsbGV2YXLDoSB1biBwb2NvIGRlIHRpZW1wbyBkaWJ1amFybG8gKCkuCgpgYGB7ciBldmFsPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdW5pdGVkX2JpZ3JhbXMgJT4lCiAgbXV0YXRlKHByZXNpZGVudCA9IGZhY3RvcihwcmVzaWRlbnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJHZW9yZ2UgV2FzaGluZ3RvbiIsICJKb2huIEFkYW1zIiwgIlRob21hcyBKZWZmZXJzb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSmFtZXMgTWFkaXNvbiIsICJKYW1lcyBNb25yb2UiLCAiSm9obiBRdWluY3kgQWRhbXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQW5kcmV3IEphY2tzb24iLCAiTWFydGluIFZhbiBCdXJlbiIsICJKb2huIFR5bGVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkphbWVzIEsuIFBvbGsiLCAiWmFjaGFyeSBUYXlsb3IiLCAiTWlsbGFyZCBGaWxsbW9yZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGcmFua2xpbiBQaWVyY2UiLCAiSmFtZXMgQnVjaGFuYW4iLCAiQWJyYWhhbSBMaW5jb2xuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFuZHJldyBKb2huc29uIiwgIlVseXNzZXMgUy4gR3JhbnQiLCAiUnV0aGVyZm9yZCBCLiBIYXllcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDaGVzdGVyIEEuIEFydGh1ciIsICJHcm92ZXIgQ2xldmVsYW5kIiwgIkJlbmphbWluIEhhcnJpc29uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIldpbGxpYW0gTWNLaW5sZXkiLCAiVGhlb2RvcmUgUm9vc2V2ZWx0IiwgIldpbGxpYW0gSG93YXJkIFRhZnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV29vZHJvdyBXaWxzb24iLCAiV2FycmVuIEcuIEhhcmRpbmciLCAiQ2FsdmluIENvb2xpZGdlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkhlcmJlcnQgSG9vdmVyIiwgIkZyYW5rbGluIEQuIFJvb3NldmVsdCIsICJIYXJyeSBTIFRydW1hbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEd2lnaHQgRC4gRWlzZW5ob3dlciIsICJKb2huIEYuIEtlbm5lZHkiLCAiTHluZG9uIEIuIEpvaG5zb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUmljaGFyZCBNLiBOaXhvbiIsICJHZXJhbGQgUi4gRm9yZCIsICJKaW1teSBDYXJ0ZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUm9uYWxkIFJlYWdhbiIsICJHZW9yZ2UgQnVzaCIsICJXaWxsaWFtIEouIENsaW50b24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiR2VvcmdlIFcuIEJ1c2giLCAiQmFyYWNrIE9iYW1hIiwgIkRvbmFsZCBUcnVtcCIsICJKb2UgQmlkZW4iKSkpICU+JQogIGNvdW50KHByZXNpZGVudCwgYmlncmFtLCBzb3J0ID0gVCkgJT4lCiAgZ3JvdXBfYnkocHJlc2lkZW50KSAlPiUKICB0b3Bfbig1KSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2woYWVzKHkgPSBuICwgeCA9IHJlb3JkZXIoYmlncmFtLG4pKSwKICAgICAgICAgICBmaWxsID0gIm1hcm9vbiIpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X3dyYXAofiBwcmVzaWRlbnQsIG5jb2wgPSA0LCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKYGBge3IgZWNobz1GLCBmaWcuY2FwPSIiLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249ImNlbnRlciIsIG91dC53aWR0aD0nMTAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS83UGFydGlkYXNEaWdpdGFsL01MZXgvbWFzdGVyL2ltYWdlL01MZXhfMDA5LnBuZyIpCmBgYAoKSGF5IG11Y2hhcyBvdHJhcyBwb3NpYmlsaWRhZGVzLCBjb21vIGVsIGFuw6FsaXNpcyBtb3Jmb2zDs2dpY28gKFBhcnQtb2YtU3BlZWNoIChQb1MpIHRhZ2dpbmcpIHF1ZSBwZXJtaXRlIGlkZW50aWZpY2FyIGEgcXXDqSBjbGFzZSBkZSBwYWxhYnJhcyBwZXJ0ZW5lY2UgY2FkYSB1bmEgZGUgZWxsYXMgKHAuIGUuLCBzdXN0YW50aXZvLCBhZGplY3Rpdm8sIHZlcmJvLCBldGMuKS4gTG8gcXVlIHNlIGNvbnNpZ3VlIGVzIGHDsWFkaXIgYSBjYWRhIHBhbGFicmEgZGVsIHRleHRvIGxhIGV0aXF1ZXRhIGdyYW1hdGljYWwuIFBlcm8gbm8gdGVuZW1vcyB0aWVtcG8uIExvIMO6bmljbyBxdWUgbWUgaW1wb3J0YSBlcyBxdWUgcXVlIGVzdGUgdGFsbGVyIG9zIGhheWEgZGVzcGVydGFkbyBlbCBpbnRlcsOpcyBwb3IgaGFjZXIgbWluZXLDrWEgZGUgdGV4dG9zIGNvbiBlbCB1c28gZGUgUi4gTGFzIHBvc2liaWxpZGFkZXMgc29uIGVub3JtZXMuIFPDqSBxdWUgbG8gaGUgbGltaXRhZG8gYSB0ZXh0b3MgZW4gaW5nbMOpcywgcGVybyBwdWVkZXMgdXNhciBsYSBsZW5ndWEgcXVlIHRlIGludGVyZXNlLgoKCjxkaXYgY2xhc3M9Indhcm5pbmciIHN0eWxlPSdwYWRkaW5nOjAuMWVtOyBiYWNrZ3JvdW5kLWNvbG9yOiMwMDgwODA7IGNvbG9yOiNmMmYyZjInPgo8c3Bhbj4KPHAgc3R5bGU9J21hcmdpbi10b3A6MWVtOyB0ZXh0LWFsaWduOmNlbnRlcic+CjxiPsKhUkVDVUVSREEhPC9iPjwvcD4KPHAgc3R5bGU9J21hcmdpbi1sZWZ0OjFlbTsnPgo8L3A+PC9zcGFuPgo8L2Rpdj4KCjxkaXY+CjxjZW50ZXI+CjxpbWcgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vN1BhcnRpZGFzRGlnaXRhbC9NTGV4L21hc3Rlci9pbWFnZS9SX2dvb2dsZS5wbmciIHdpZHRoPSI1MDAiLz4KPGNlbnRlcj4KPC9kaXY+CmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNlc3Npb25JbmZvKCkKYGBgCgo8ZGl2IGNsYXNzPSJ3YXJuaW5nIiBzdHlsZT0ncGFkZGluZzowLjFlbTsgYmFja2dyb3VuZC1jb2xvcjojODA0MDAwOyBjb2xvcjojZjJmMmYyJz4KPHNwYW4+CjxwIHN0eWxlPSdtYXJnaW4tdG9wOjFlbTsgdGV4dC1hbGlnbjpjZW50ZXInPgo8Yj5OT1RFPC9iPgo8YnI+RXN0ZSBjdWFkZXJubyBzZSBiYXNhIGVuIGFsZ3Vub3MgdHV0b3JpYWxlcyBkZSBbTGFkYWxdKGh0dHBzOi8vbGFkYWwuZWR1LmF1L3R1dG9yaWFscy5odG1sKSwgW1Byb2dyYW1taW5nIEhpc3Rvcmlhbl0oaHR0cHM6Ly9wcm9ncmFtbWluZ2hpc3Rvcmlhbi5vcmcvZW4vbGVzc29ucy9iYXNpYy10ZXh0LXByb2Nlc3NpbmctaW4tcikgeSBbQ3VlbnRhUGFsYWJyYXNdKGh0dHA6Ly93d3cuYWljLnV2YS5lcy9jdWVudGFwYWxhYnJhcy8pLjxicj5QYXJhIHF1aWVuZXMgdGVuZ2FuIGludGVyZXNlcyBsaW5nw7zDrXN0aWNvcywgcmVjb21pZW5kbyBsb3MgdHV0b3JpYWxlcyBMYWRhbC48L3A+CjxwIHN0eWxlPSdtYXJnaW4tbGVmdDoxZW07Jz4KPC9wPjwvc3Bhbj4KPC9kaXY+Cgo8ZGl2Pgo8Y2VudGVyPgo8aW1nIHNyYz0iaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzdQYXJ0aWRhc0RpZ2l0YWwvTUxleC9tYXN0ZXIvaW1hZ2UvRVhQTElDSVQtN1BBUlRJREFTLnBuZyIvPgo8YnI+UElEMjAyMC0xMTI2MjFHQi1JMDAvQUVJLzEwLjEzMDM5LzUwMTEwMDAxMTAzMwo8L2NlbnRlcj4KPC9kaXY+Cg==